【用户登录】基于微信公众号实现登录注册

       

目录

前期准备

1、公众号申请

2、测试号申请

3、填写微信服务的基本配置信息、

服务开发

1、接入微信

测试:

2、内网穿透

3、监听公众号消息

测试

业务开发

XML解析工具类


        大家好,我是jstart千语。现在主流的登录注册方式有短信验证登录、邮箱登录、微信扫码登录。但这些方式对于初学者来说成本过于高,比如短信登录需要和云平台对接,邮箱登录的配置也很麻烦。所以接下来我介绍一种基于微信公众号的方式进行登录注册的实现,这是一种成本很低的登录方式,只需要申请一个公众号认证即可,在开发阶段也可以申请一个测试的公众号,开发起来非常方便。

        主要流程就是:用户关注公众号——>服务器端获取到用户唯一的openId——>用户给公众号发送“验证码”——>后端通过公众号给客户端发送合法的验证码——>用户输入验证码——>后端验证——>拿用户唯一的openId作为账号进行自动注册,给用户登录

        这里其实用户关注公众号后就可以直接拿到openId,然后直接给用户登录或者注册了,这里为了让大家更好地理解公众号的对接流程(文本发送之类的),所以还采用了一个验证码的流程。 接下来主要涉及到与微信对接、监听公众号的状态、内网穿透



前期准备

1、公众号申请

首先进入微信公众平台:微信公众平台https://mp.weixin.qq.com/

(1)右上角点击立即注册

官方文档指引:(个人)注册公众平台步骤 - 腾讯客服

(2)选择公众号

(3)按照流程一步一步注册申请即可:


2、测试号申请

 (1)进入公众号开发者的文档:开发前必读 / 首页

(2)申请测试号:微信公众平台

3、填写微信服务的基本配置信息、

官方文档其实写的不太行,没有java版的:开始开发 / 接入指南



服务开发

1、接入微信

       上面的url其中一个用处就是用来与微信接入的,但是这个url需要是公网上微信能访问的,在开发阶段项目肯定还没有上线的,所以可以使用内网穿透,内网穿透下面有讲。

(1)对接时:微信会对这个路径发送get请求(关注、文本消息是发送post请求到这个url)

直接自定义一个接口,注意请求路径uri要保持与上面设置的一致:

官方文档:开始开发 / 接入指南

    //微信token、需要与公众号上配置的一致
    private static final String token = "da4gdf5"; 

    @GetMapping("/wx/callBack")
    public String callback(@RequestParam("signature") String signature,
                           @RequestParam("timestamp") String timestamp,
                           @RequestParam("nonce") String nonce,
                           @RequestParam("echostr") String echostr) {
        log.info("请求参数:signature:{};timestamp:{};nonce:{};echostr:{};",
                signature, timestamp, nonce, echostr);

        String sha1Str = SHA1.generateSHA1Signature(token, timestamp, nonce);
        log.info("sha1Str:{}", sha1Str);
        if (signature.equals(sha1Str)) {
            return echostr;
        }

        return "unknown";
    }

可以看到微信的get请求会发送四个请求参数,分别有什么作用呢

对接流程:

1、将token、timestamp、nonce三个参数进行字典序排序,放入数组中

2、然后将他们拼接成一个字符串,进行sha1加密,得到密文

3、将密文与参数中的signature比较,如果相同则说明这个请求确实是微信发过来的

4、校验通过,将形参中的 echostr 原样地进行返回

5、微信收到原样返回的echostr,接入成功

(2)将参数排序,拼接成字符串,然后再进行加密都封装进一个工具类中:

package com.jingdianjichi.wx.utils;

import lombok.extern.slf4j.Slf4j;

import java.security.MessageDigest;
import java.util.Arrays;

/**
 * sha1生成签名工具
 *
 * @author: ChickenWing
 * @date: 2023/11/5
 */
@Slf4j
public class SHA1 {

    /**
     * 生成SHA1签名
     * @param token 公众号的token
     * @param timestamp 微信服务器传过来的时间戳
     * @param nonce 微信服务器传过来的随机数
     * @return SHA1加密后的签名
     */
    public static String generateSHA1Signature(String token, String timestamp, String nonce) {
        // 1. 将token、timestamp、nonce三个参数按字典序排序
        String[] params = new String[]{token, timestamp, nonce};
        Arrays.sort(params);

        // 2. 拼接成一个字符串
        StringBuilder sb = new StringBuilder();
        for (String param : params) {
            sb.append(param);
        }

        // 3. 使用SHA1进行加密
        String signature = sha1(sb.toString());
        return signature;
    }

    /**
     * 对字符串进行SHA1加密
     * @param input 输入字符串
     * @return SHA1加密后的结果
     */
    private static String sha1(String input) {
        try {
            // 获取SHA1 MessageDigest实例
            MessageDigest digest = MessageDigest.getInstance("SHA-1");

            // 执行SHA1加密
            byte[] hash = digest.digest(input.getBytes("UTF-8"));

            // 将字节数组转化为十六进制字符串
            StringBuilder hexString = new StringBuilder();
            for (byte b : hash) {
                // 将每个字节转化为两位十六进制字符串
                String hex = Integer.toHexString(0xFF & b);
                if (hex.length() == 1) {
                    hexString.append('0');
                }
                hexString.append(hex);
            }

            return hexString.toString().toLowerCase();  // 返回小写形式的SHA1签名
        } catch (Exception e) {
            throw new RuntimeException("SHA1加密失败", e);
        }
    }

    public static void main(String[] args) {
        // 测试用例
        String token = "your_token";  // 公众号的token
        String timestamp = "timestamp_from_wechat";  // 微信服务器传过来的时间戳
        String nonce = "nonce_from_wechat";  // 微信服务器传过来的随机数
        String echostr = "echostr_from_wechat";  // 微信服务器传过来的随机字符串

        String signature = generateSHA1Signature(token, timestamp, nonce);
        System.out.println("SHA1 Signature: " + signature);
    }
}

至此微信的对接就没问题了。如果报了异常,可以查看微信定义的全局返回码:开发前必读 / 全局返回码说明


测试:


2、内网穿透

这个get接口是给微信用的,所以要用内网穿透给微信调用得到。

(1)进入内网穿透的官方教程文档:

NATAPP1分钟快速新手图文教程 - NATAPP-内网穿透 基于ngrok的国内高速内网映射工具https://natapp.cn/article/natapp_newbie

(2)下载内网穿透的客户端:NATAPP-内网穿透 基于ngrok的国内高速内网映射工具

(3)下载之后,解压至任意目录,得到natapp.exe (linux下无需解压,直接 wget)

(4)下载配置文件使用本地配置文件config.ini - NATAPP-内网穿透 基于ngrok的国内高速内网映射工具

(5)将配置文件放到刚刚解压出来的内网穿透的客户端同目录下

(6)将内网穿透的authtoken复制,写入配置文件中

(7)windows系统的话直接双击运行exe文件

(8)然后就可以将这个地址复制到上面微信公众号服务配置的url里了

(9)测试



3、监听公众号消息

        刚刚使用的是get请求接入微信。当公众号被关注、用户发消息给了公众号等,微信也会对同样的请求路径发送请求,但请求方式是post

        而且,公众号通过post请求发送过来的请求体,不再是json格式的,而是XML格式的了

        事后要对XML格式的数据进行解析,因为XML数据里面就包含了用户唯一的openId,还有用户发送了什么消息。

详细官方文档(可以去看看,很详细):基础消息能力 / 接收普通消息

(1)微信发送的xml示例代码:下面是当用户关注了公众号时微信发送来的请求体

<xml>
<ToUserName><![CDATA[gh_a0946b2a49d2]]></ToUserName>
<FromUserName><![CDATA[ofo2kfilifn******fk4ion7oBIcY]]></FromUserName>
<CreateTime>1742795741</CreateTime>
<MsgType><![CDATA[event]]></MsgType>
<Event><![CDATA[subscribe]]></Event>
<EventKey><![CDATA[]]></EventKey>
</xml>

里面的类型代表什么?:

(2)当用户发送“验证码”给公众号时,微信发过来的请求体:

<xml>
<ToUserName><![CDATA[gh_a0946b2a49d2]]></ToUserName>
<FromUserName><![CDATA[ofo2kfilifn******fk4ion7oBIcY]]></FromUserName>
<CreateTime>1742796773</CreateTime>
<MsgType><![CDATA[text]]></MsgType>
<Content><![CDATA[验证码]]></Content>
<MsgId>24950849497629663</MsgId>
</xml>

里面的类型代表什么?:

示例代码(解析xml的工具类在文末):

@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 msg_signature) {
        

        log.info("\n微信回调请求参数:requestBody:{};\nsignature:{};timestamp:{};nonce:{};echostr:{};",
                requestBody, signature, timestamp, nonce, msg_signature);

        //工具类解析 xml格式文本
        Map<String, String> msgMap = MessageUtil.parseXml(requestBody);

        //获取消息类型:
        String msgType = msgMap.get("MsgType");
        StringBuilder msgTypeKeySB = new StringBuilder();
        msgTypeKeySB.append(msgType);
        String event = msgMap.get("Content");//用户发送了文本消息时,event才不为空,否则为null
        if (!StringUtils.isEmpty(event)){
            msgTypeKeySB.append(".");
            msgTypeKeySB.append(event);
        }

        log.info("msg:{}", msgTypeKeySB.toString());
        
}

 上面这段代码,将用户的行为再取出来打印了一下日志做测试。


测试

可以自己扫一下自己那个测试号的二维码,然后点击关注或者发送一下文本消息看看

(1)用户发送“验证码” 给公众号时:



业务开发

        可以看到xml中都有了ToUserName和FromUserName,微信服务给我们发过来的,其中ToUserName就是指我们的服务端,FromUserName就是指用户端,里面的内容就是用户唯一的openId。

        所以我们想要给用户发送验证码时,只要模仿微信发过来的文本类型消息,也这样写类似的XML信息即可,然后XML信息里:ToUserName改为原来的FromUserName,FromUserName改为原来的ToUserName,这就表示是服务端给用户端发送请求了

XML代码示例:

这是抽取出来的一个方法,业务中传入解析xml返回的map参数,然后xml中的信息表示给用户发送欢迎消息。

 @Override
    public String dealMsg(Map<String, String> messageMap) {
        log.info("触发用户关注事件");

        String UserName = messageMap.get("FromUserName");
        String adminName = messageMap.get("ToUserName");

        String content = "感谢您的关注";

        String backMsg = "<xml>\n" +
                "  <ToUserName><![CDATA[" + UserName + "]]></ToUserName>\n" +
                "  <FromUserName><![CDATA[" + adminName + "]]></FromUserName>\n" +
                "  <CreateTime>12345678</CreateTime>\n" +
                "  <MsgType><![CDATA[text]]></MsgType>\n" +
                "  <Content><![CDATA[" + content + "]]></Content>\n" +
                "</xml>";
        return backMsg;
    }

至于具体的业务这里就不演示扩展了,比如可以生成一个验证码,然后放到redis里面,设置过期时间,然后将验证码通过公众号发送给用户,然后用户输入验证码,后端给他登录注册。



XML解析工具类

需要引入三个依赖:

<!--dom4j-->
<dependency>
  <groupId>org.dom4j</groupId>
  <artifactId>dom4j</artifactId>
  <version>2.1.3</version>
</dependency>
<!--xstream 也是用于解析xml文本的-->
<dependency>
  <groupId>com.thoughtworks.xstream</groupId>
  <artifactId>xstream</artifactId>
  <version>1.4.10</version>
</dependency>
<!--序列化-->
<dependency>
  <groupId>com.google.code.gson</groupId>
  <artifactId>gson</artifactId>
</dependency>

工具类: 

package com.jingdianjichi.wx.utils;



import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

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;
    }

}

### 实现微信登录功能 #### OAuth2授权流程 为了使用户能够在微信公众号内置浏览器中完成登录操作,需遵循OAuth2协议。具体来说,在前端页面加载时应重定向至特定的微信授权链接[^3]: ```javascript let redirect_uri = encodeURI(window.location.href).split('#')[0], appid = "YOUR_APP_ID"; // 替换成实际应用ID let wx_url = `https://open.weixin.qq.com/connect/oauth2/authorize?appid=${appid}&redirect_uri=${redirect_uri}&response_type=code&scope=snsapi_userinfo&state=STATE#wechat_redirect`; window.location.href = wx_url; ``` 此代片段会引导用户的设备访问由`wx_url`指定的位置,从而触发微信服务器向第三方应用程序发送临时凭证(即`code`),该凭证稍后可用于交换更持久性的令牌。 #### 后端处理逻辑 一旦接收到带有`code`参数的回调请求,服务端应当立即利用这个一次性去换取access_token以及其他必要的个人信息。以下是基于Golang编写的简化版控制器函数来构建这样的响应机制[^2]: ```go // AuthLogin @Title 授权URL // @router /authLogin [post,get] func (u *UserController) AuthLogin() { url := fmt.Sprintf("https://open.weixin.qq.com/connect/oauth2/authorize?appid=%s&redirect_uri=%s&response_type=code&scope=snsapi_userinfo&state=200#wechat_redirect", appid, baseUrl) u.Success(0, url, "成功") } ``` 请注意替换其中的变量值以匹配具体的项目环境配置。 #### JS-SDK初始化与权限声明 为了让网页能够调用微信提供的JavaScript API,开发者还需要提前准备好一系列准备工作,比如注册并启用相应的API接口名称列表。对于地理位置相关的交互而言,则至少要包含如下所示的内容[^1]: ```json { "jsApiList": ["openLocation","getLocation"] } ``` 同时确保已按照官方指引完成了JS-SDK的安全域名备案工作,并通过有效的签名算法验证当前页面合法性之后再执行任何敏感的操作。 #### 注意事项 - **安全性考量**:在整个过程中务必妥善保管各类密钥信息,防止泄露给未经授权方;另外建议采用HTTPS加密传输方式保障数据安全。 - **调试工具的应用**:可以借助微信公众平台提供的在线沙盒账号来进行初步的功能测试,方便快捷地获取到所需的AppId和AppSecret用于本地模拟运行期间。 - **多团队协作管理**:当存在多名成员共同参与同一个项目的开发周期内,记得各自独立创建专属的小程序实例以便于分工合作而不至于混淆彼此之间的资源文件版本控制等问题的发生。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值