【SpringBoot】-- 项目实现微信公众号扫码登录

目录

一、业务需求

二、内网穿透

三、服务器配置

​编辑

四、依赖引入

pom.xml

五、验证服务器有效性

代码

controller类

SHA1工具类

六、用户订阅后自动回复消息

代码

controller类

 MessageUtil工具类

七、用户发送文本消息后回复消息

代码

controller类

八、拓展


一、业务需求

手机扫描二维码后关注公众号,发送验证码给公众号,公众号返回验证码,然后输入到网页判断验证码是否正确后通过登录。

二、内网穿透

内网穿透是,它允许通过公共网络(比如互联网)将数据传输到内部网络中,即使内部网络处于防火墙或NAT(网络地址转换)等保护措施之后也能实现。这对于需要从外部访问内部网络资源的场景非常有用,使用场景是外部网络访问内部网络的情况,比如远程访问家庭网络中的设备。

这里使用的内网穿透工具是natapp

内网穿透使用指南:

NATAPP1分钟快速新手图文教程 - NATAPP-内网穿透 基于ngrok的国内高速内网映射工具

输入命令启动:start natapp -authtoken=xxxx

启动后会有一个地址

三、服务器配置

测试号地址:微信公众平台

这里的Token暂可随便输入

四、依赖引入

pom.xml

  <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <java.version>1.8</java.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <spring-boot.version>2.4.2</spring-boot.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <exclusions>
                <exclusion>
                    <artifactId>spring-boot-starter-logging</artifactId>
                    <groupId>org.springframework.boot</groupId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.16</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-log4j2</artifactId>
        </dependency>
        <!-- json处理-->
        <dependency>
            <groupId>com.thoughtworks.xstream</groupId>
            <artifactId>xstream</artifactId>
            <version>1.4.18</version>
        </dependency>
        <dependency>
            <groupId>com.google.code.gson</groupId>
            <artifactId>gson</artifactId>
            <version>2.8.5</version>
        </dependency>
        <dependency>
            <groupId>org.dom4j</groupId>
            <artifactId>dom4j</artifactId>
            <version>2.1.1</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-core</artifactId>
            <version>2.12.7</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.12.7</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-pool2</artifactId>
            <version>2.9.0</version>
        </dependency>

    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>${spring-boot.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <repositories>
        <repository>
            <id>central</id>
            <name>aliyun maven</name>
            <url>http://maven.aliyun.com/nexus/content/groups/public/</url>
            <layout>default</layout>
            <releases>
                <enabled>true</enabled>
            </releases>
            <snapshots>
                <enabled>true</enabled>
            </snapshots>
        </repository>
    </repositories>
    <build>
        <finalName>${project.artifactId}</finalName>
        <!--打包成jar包时的名字-->
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>2.3.0.RELEASE</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

五、验证服务器有效性

开发者提交信息后,微信服务器将发送GET请求到填写的服务器地址URL上,GET请求携带参数如下表所示:

参数

描述

signature

微信加密签名,signature结合了开发者填写的token参数和请求中的timestamp参数、nonce参数。

timestamp

时间戳

nonce

随机数

echostr

随机字符串

开发者通过检验signature对请求进行校验(下面有校验方式)。若确认此次GET请求来自微信服务器,请原样返回echostr参数内容,则接入生效,成为开发者成功,否则接入失败。加密/校验流程如下:

1)将token、timestamp、nonce三个参数进行字典序排序

2)将三个参数字符串拼接成一个字符串进行sha1加密

3)开发者获得加密后的字符串可与signature对比,标识该请求来源于微信

代码

controller类

这里的token需要和上面服务器配置的token一致

    private static final String token = "aeaae";

    /**
     * 回调消息校验
     */
    @GetMapping("callback")
    public String callback(@RequestParam("signature") String signature,
                           @RequestParam("timestamp") String timestamp,
                           @RequestParam("nonce") String nonce,
                           @RequestParam("echostr") String echostr){
        log.info("get验签请求参数:signature:{},timestamp:{},nonce:{},echostr:{}",
                signature,timestamp,nonce,echostr);
        String shaStr = SHA1.getSHA1(token,timestamp,nonce,"");
        if(signature.equals(shaStr)){
            return echostr;
        }
        return "unknown";
    }

SHA1工具类

/**
 * sha1生成签名工具
 */
@Slf4j
public class SHA1 {

    /**
     * 用SHA1算法生成安全签名
     *
     * @param token     票据
     * @param timestamp 时间戳
     * @param nonce     随机字符串
     * @param encrypt   密文
     * @return 安全签名
     */
    public static String getSHA1(String token, String timestamp, String nonce, String encrypt) {
        try {
            String[] array = new String[]{token, timestamp, nonce, encrypt};
            StringBuffer sb = new StringBuffer();
            // 字符串排序
            Arrays.sort(array);
            for (int i = 0; i < 4; i++) {
                sb.append(array[i]);
            }
            String str = sb.toString();
            // SHA1签名生成
            MessageDigest md = MessageDigest.getInstance("SHA-1");
            md.update(str.getBytes());
            byte[] digest = md.digest();

            StringBuffer hexStr = new StringBuffer();
            String shaHex = "";
            for (int i = 0; i < digest.length; i++) {
                shaHex = Integer.toHexString(digest[i] & 0xFF);
                if (shaHex.length() < 2) {
                    hexStr.append(0);
                }
                hexStr.append(shaHex);
            }
            return hexStr.toString();
        } catch (Exception e) {
            log.error("sha加密生成签名失败:", e);
            return null;
        }
    }
}

六、用户订阅后自动回复消息

用户在关注与取消关注公众号时,微信会把这个事件推送到开发者填写的URL。方便开发者给用户下发欢迎消息或者做账号的解绑。为保护用户数据隐私,开发者收到用户取消关注事件时需要删除该用户的所有信息。

<xml>
  <ToUserName><![CDATA[toUser]]></ToUserName>
  <FromUserName><![CDATA[FromUser]]></FromUserName>
  <CreateTime>123456789</CreateTime>
  <MsgType><![CDATA[event]]></MsgType>
  <Event><![CDATA[subscribe]]></Event>
</xml>

参数

描述

ToUserName

开发者微信号

FromUserName

发送方账号(一个OpenID)

CreateTime

消息创建时间 (整型)

MsgType

消息类型,event

Event

事件类型,subscribe(订阅)、unsubscribe(取消订阅)

代码

controller类

 @PostMapping(value = "callback",produces = "application/xml;charset=UTF-8")
    public String callback( @RequestBody String requestBody,
                            @RequestParam("signature") String signature,
                            @RequestParam("timestamp") String timestamp,
                            @RequestParam("nonce") String nonce,
                            @RequestParam(value = "msg_signature", required = false) String msgSignature){

        //解析微信发来的xml
        Map<String,String> messageMap = MessageUtil.parseXml(requestBody);
        //获取事件类型和消息类型
        String msgType = messageMap.get("MsgType");
        String event = messageMap.get("Event") == null ? "" : messageMap.get("Event");
        //拼接
        StringBuilder sb = new StringBuilder();
        sb.append(msgType);
        if(!StringUtils.isEmpty(event)){
            sb.append(".");
            sb.append(event);
        }
        String msgTypeKey = sb.toString();
        //用户关注事件
        if(msgTypeKey.equals("event.subscribe")){
            return dealMsg(messageMap);
        }
        //用户发送文本消息
        else if(msgTypeKey.equals("text")){
            return dealUserMsg(messageMap);
        }

        return "unknown";
    }

    //用户关注事件
    public String dealMsg(Map<String,String> messageMap){
        String fromUserName = messageMap.get("FromUserName");
        String toUserName = messageMap.get("ToUserName");
        String subscribeContent = "感谢您的关注,我是四月天行健";
        String content = "<xml>\n" +
                "  <ToUserName><![CDATA[" + fromUserName + "]]></ToUserName>\n" +
                "  <FromUserName><![CDATA[" + toUserName + "]]></FromUserName>\n" +
                "  <CreateTime>12345678</CreateTime>\n" +
                "  <MsgType><![CDATA[text]]></MsgType>\n" +
                "  <Content><![CDATA[" + subscribeContent + "]]></Content>\n" +
                "</xml>";
        return content;
    }

 MessageUtil工具类

public class MessageUtil {

    /**
     * 解析微信发来的请求(XML).
     *
     * @param msg 消息
     * @return map
     */
    public static Map<String, String> parseXml(final String msg) {
        // 将解析结果存储在HashMap中
        Map<String, String> map = new HashMap<String, String>();

        // 从request中取得输入流
        try (InputStream inputStream = new ByteArrayInputStream(msg.getBytes(StandardCharsets.UTF_8.name()))) {
            // 读取输入流
            SAXReader reader = new SAXReader();
            Document document = reader.read(inputStream);
            // 得到xml根元素
            Element root = document.getRootElement();
            // 得到根元素的所有子节点
            List<Element> elementList = root.elements();

            // 遍历所有子节点
            for (Element e : elementList) {
                map.put(e.getName(), e.getText());
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        return map;
    }

}

七、用户发送文本消息后回复消息

当用户发送消息给公众号时(或某些特定的用户操作引发的事件推送时),会产生一个POST请求,开发者可以在响应包(Get)中返回特定XML结构,来对该消息进行响应(现支持回复文本、图片、图文、语音、视频、音乐)。严格来说,发送被动响应消息其实并不是一种接口,而是对微信服务器发过来消息的一次回复。

<xml>
  <ToUserName><![CDATA[toUser]]></ToUserName>
  <FromUserName><![CDATA[fromUser]]></FromUserName>
  <CreateTime>12345678</CreateTime>
  <MsgType><![CDATA[text]]></MsgType>
  <Content><![CDATA[你好]]></Content>
</xml>

参数

是否必须

描述

ToUserName

接收方账号(收到的OpenID)

FromUserName

开发者微信号

CreateTime

消息创建时间 (整型)

MsgType

消息类型,文本为text

Content

回复的消息内容(换行:在content中能够换行,微信客户端就支持换行显示)

代码

controller类

    //用户发送文本事件
    public String dealUserMsg(Map<String,String> messageMap){
        String content = messageMap.get("Content");
        if (!"验证码".equals(content)) {
            return "";
        }
        String fromUserName = messageMap.get("FromUserName");
        String toUserName = messageMap.get("ToUserName");

        Random random = new Random();
        int num = random.nextInt(1000);
        String numContent = "您当前的验证码是:" + num + "! 5分钟内有效";
        String replyContent = "<xml>\n" +
                "  <ToUserName><![CDATA[" + fromUserName + "]]></ToUserName>\n" +
                "  <FromUserName><![CDATA[" + toUserName + "]]></FromUserName>\n" +
                "  <CreateTime>12345678</CreateTime>\n" +
                "  <MsgType><![CDATA[text]]></MsgType>\n" +
                "  <Content><![CDATA[" + numContent + "]]></Content>\n" +
                "</xml>";
        return replyContent;
    }

八、拓展

可以将验证码和微信返回的openid存储到redis中,然后在需要时可以从redis中取出数据,完成登录。

  • 11
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Flask是一种轻量级的Python Web框架,能够方便地构建网页应用程序。要实现微信公众号扫码关注并登录网页功能,可以使用Flask结合微信开发接口进行实现。 首先,需要在微信公众平台上注册并创建一个公众号,并获取到相关的开发者ID和密钥。 接下来,搭建Flask应用程序,先安装Flask库,并导入相关的依赖库,如wechatpy和requests库等。 然后,创建一个Flask路由,用于接收微信服务器的验证请求和菜单跳转等请求。根据微信开发文档,编写逻辑代码,进行验证和处理微信服务器的各种请求。 在处理菜单跳转请求时,可以通过微信JS-SDK提供的扫一扫功能,生成一个特定的二维码,用于用户关注公众号。二维码中包含一个唯一的标识码,用于识别用户和公众号之间的关系。 当用户扫描二维码关注公众号后,微信服务器会向事先设置的回调URL发送消息通知。在Flask应用程序中,可以通过编写对应的路由来处理该通知,获取到用户的唯一标识码。然后,可以将该标识码与用户相关的信息存储到数据库中,以便后续使用。 最后,通过编写相关的路由和模板,实现用户登录网页功能。当用户点击网页中的登录按钮时,可以跳转到微信授权登录页面。用户授权后,微信会将用户的唯一标识码和相关信息返回到事先设置的回调URL。在Flask应用程序中,处理该回调URL的路由中,可以获取到用户的标识码,从数据库中获取用户信息,并进行登录操作。 综上所述,通过使用Flask框架结合微信开发接口,可以实现微信公众号扫码关注并登录网页功能。这样的实现方式能够方便地与微信公众号进行交互,并提供给用户一个方便、安全的登录方式。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

四月天行健

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值