实现微信公众号扫码登录全过程(最详细)

TOC(真心顶不住微信公众号开发文档的乱并且刚接触的开发者绝对一头雾水,决心编写,防止更多人跳坑!)

前期准备

注册并配置微信公众号,获取 AppID 和 AppSecret。
配置开发者模式,设置服务器 URL 和 Token。(这一步以下会细讲)

认证的公众号

认证的公众号一年年费 300RMB,可以拥有“生成带参数的二维码”这一项接口权限,以及支付借口权限等具体可以去官方文档登录后查看 “设置与开发”-》“接口权限”
认证号扫描登录流程图

配置准备

在 springboot 中 application.yml 配置微信公众号配置,appid、secret、token、aesKey,以及回调的接口 callback

1
2
3
4
5
6
7
8
wx:
mp:
callback:
configs:
- appId: # 第一个公众号的appid
secret: # 公众号的appsecret
token: # 接口配置里的Token值
aesKey: # 接口配置里的EncodingAESKey值

在 pom.xml 添加微信公众号开发的架包(或者自己写也可省了略)

1
2
3
4
<dependency>
<groupId>com.github.binarywang</groupId>
<artifactId>weixin-java-mp</artifactId>
</dependency>

初始化 WxMpService

在配置类中初始化 WxMpService 类的方法返回 server 注入 Bean 中,如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Bean
public WxMpService wxMpService() {
final List<WxMpProperties.MpConfig> configs = this.properties.getConfigs();//这里是获取application.yml配置信息,可以自己写方法获取
if (configs == null) {
throw new RuntimeException("没有配置微信公众号信息!");
}

WxMpService service = new WxMpServiceImpl();
service.setMultiConfigStorages(configs
.stream().map(a -> {
WxMpDefaultConfigImpl configStorage;
configStorage = new WxMpDefaultConfigImpl();
configStorage.setAppId(a.getAppId());
configStorage.setSecret(a.getSecret());
configStorage.setToken(a.getToken());
configStorage.setAesKey(a.getAesKey());
return configStorage;
}).collect(Collectors.toMap(WxMpDefaultConfigImpl::getAppId, a -> a, (o, n) -> o)));
return service;
}

核心代码

可以使用 Cpolar Web UI 来做内网穿透进行测试,具体安装和设置可以在本站搜索“Cpolar 内网穿透”,没有的只能发布自己服务器进行 url 配置

  1. 首先先在微信公众号填写服务器配置
    服务器配置

    服务器地址(URL)一定要外网能访问,并且验证签名后返回 echostr,服务器配置的 token 和公众号的 token 要一致,这样才能在公众号上配置成功

    配置完后一定要启用,不然无法生效

    验证代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* 微信认证配置接口
*
* @param signature
* @param timestamp
* @param nonce
* @param echostr
* @return
*/
@GetMapping(value = "/authGet", produces = "text/plain;charset=utf-8")
public String authGet(@RequestParam(name = "signature", required = false) String signature, @RequestParam(name = "timestamp", required = false) String timestamp, @RequestParam(name = "nonce", required = false) String nonce, @RequestParam(name = "echostr", required = false) String echostr) {
if (StringUtils.isAnyBlank(signature, timestamp, nonce, echostr)) {
throw new IllegalArgumentException("请求参数非法,请核实!");
}


if (wxService.checkSignature(timestamp, nonce, signature)) {
return echostr;
}

return "非法请求";
}
  1. 生成和请求一张带参数的二维码,返回给前端扫码
1
2
//请求微信接口,获取登录码地址
WxMpQrCodeTicket wxMpQrCodeTicket = wxMpService.getQrcodeService().qrCodeCreateTmpTicket(code, (int) EXPIRE_TIME.getSeconds());//code为携带的参数,(int) EXPIRE_TIME.getSeconds()为过期时间
  1. 扫码后,微信回调服务器接口;
    有两种情况 1.如果用户还未关注公众号,则用户可以关注公众号,关注后微信会将带场景值关注事件推送给开发者。 2.如果用户已经关注公众号,在用户扫描后会自动进入会话,微信也会将带场景值扫描事件推送给开发者。
    post 的方式推送事件:1、事件推送将会推送给公众号在公众平台官网开发者中心设置的服务地址中 2、如果公众号已将账号管理权限集(因为该接口权限从属于账号管理权限集)授权给第三方平台,那么将由第三方平台代公众号接收事件推送,具体是推送到第三方平台的公众号消息与事件接收 URL
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
 String openid = wxMpXmlMessage.getFromUser();
Integer loginCode = Integer.parseInt(getEventKey(wxMpXmlMessage));
User user = userDao.getUserByOpenId(openid); // 根据openid获取用户
//如果已经注册,直接登录成功
if (Objects.nonNull(user) && StringUtils.isNotEmpty(user.getAvatar())) {
...返回前端更新登录状态等逻辑操作
return;
}

//user为空先注册,手动生成,以保存uid
if (Objects.isNull(user)) {
...先根据openid进行用户注册,即可
}
//在redis中保存openid和场景code的关系,后续才能通知到前端,旧版数据没有清除,这里设置了过期时间
RedisUtils.set(RedisKey.getKey(RedisKey.OPEN_ID_STRING, openid), loginCode, 60, TimeUnit.MINUTES);
//授权流程,给用户发送授权消息,并且异步通知前端扫码成功,等待授权
...{返回前端逻辑}

//URL为回调地址
String skipUrl = String.format(URL, wxMpService.getWxMpConfigStorage().getAppId(), URLEncoder.encode(callback + "/wx/portal/public/callBack"));
WxMpXmlOutMessage.TEXT().build();
return new TextBuilder().build("请点击链接授权:<a href=\"" + skipUrl + "\">登录</a>", wxMpXmlMessage, wxMpService);//给发送者当前openid的微信公众上发送登录消息

回调接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
 String openid = wxMpXmlMessage.getFromUser();
Integer loginCode = Integer.parseInt(getEventKey(wxMpXmlMessage));
User user = userDao.getUserByOpenId(openid); // 根据openid获取用户
//如果已经注册,直接登录成功
if (Objects.nonNull(user) && StringUtils.isNotEmpty(user.getAvatar())) {
...返回前端更新登录状态等逻辑操作
return;
}

//user为空先注册,手动生成,以保存uid
if (Objects.isNull(user)) {
...先根据openid进行用户注册,即可
}
//在redis中保存openid和场景code的关系,后续才能通知到前端,旧版数据没有清除,这里设置了过期时间
RedisUtils.set(RedisKey.getKey(RedisKey.OPEN_ID_STRING, openid), loginCode, 60, TimeUnit.MINUTES);
//授权流程,给用户发送授权消息,并且异步通知前端扫码成功,等待授权
...{返回前端逻辑}

//URL为回调地址
String skipUrl = String.format(URL, wxMpService.getWxMpConfigStorage().getAppId(), URLEncoder.encode(callback + "/wx/portal/public/callBack"));
WxMpXmlOutMessage.TEXT().build();
return new TextBuilder().build("请点击链接授权:<a href=\"" + skipUrl + "\">登录</a>", wxMpXmlMessage, wxMpService);//给发送者当前openid的微信公众上发送登录消息

未认证公众号

未认证的公众号的步骤基本差不多,差别就是没有生成带参数的二维码的权限,通过“接收普通消息”和“接收事件推送”这两个接口权限去验证 openid 用户。主要两种方式:1.前端提供参数扫码关注公众号后回复参数校验通过。2.关注公众号后开发者推送参数校验,在前端填写后登录。实现方式基本一致,就不分开讲述。这里主要讲前端提供参数扫码关注公众号后回复参数校验通过。

前端提供参数扫码关注公众号后回复参数校验通过

首先需要做的步骤和上述差不多

配置准备

在 springboot 中 application.yml 配置微信公众号配置,appid、secret、token、aesKey,以及回调的接口 callback

1
2
3
4
5
6
7
8
wx:
mp:
callback:
configs:
- appId: # 第一个公众号的appid
secret: # 公众号的appsecret
token: # 接口配置里的Token值
aesKey: # 接口配置里的EncodingAESKey值

在 pom.xml 添加微信公众号开发的架包(或者自己写也可省了略)

1
2
3
4
<dependency>
<groupId>com.github.binarywang</groupId>
<artifactId>weixin-java-mp</artifactId>
</dependency>

初始化 WxMpService

在配置类中初始化 WxMpService 类的方法返回 server 注入 Bean 中,如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Bean
public WxMpService wxMpService() {
final List<WxMpProperties.MpConfig> configs = this.properties.getConfigs();//这里是获取application.yml配置信息,可以自己写方法获取
if (configs == null) {
throw new RuntimeException("没有配置微信公众号信息!");
}

WxMpService service = new WxMpServiceImpl();
service.setMultiConfigStorages(configs
.stream().map(a -> {
WxMpDefaultConfigImpl configStorage;
configStorage = new WxMpDefaultConfigImpl();
configStorage.setAppId(a.getAppId());
configStorage.setSecret(a.getSecret());
configStorage.setToken(a.getToken());
configStorage.setAesKey(a.getAesKey());
return configStorage;
}).collect(Collectors.toMap(WxMpDefaultConfigImpl::getAppId, a -> a, (o, n) -> o)));
return service;
}

核心代码

可以使用 Cpolar Web UI 来做内网穿透进行测试,具体安装和设置可以在本站搜索“Cpolar 内网穿透”,没有的只能发布自己服务器进行 url 配置

  1. 首先先在微信公众号填写服务器配置
    服务器配置

    服务器地址(URL)一定要外网能访问,并且验证签名后返回 echostr,服务器配置的 token 和公众号的 token 要一致,这样才能在公众号上配置成功

    配置完后一定要启用,不然无法生效

    验证代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* 微信认证配置接口
*
* @param signature
* @param timestamp
* @param nonce
* @param echostr
* @return
*/
@GetMapping(value = "/authGet", produces = "text/plain;charset=utf-8")
public String authGet(@RequestParam(name = "signature", required = false) String signature, @RequestParam(name = "timestamp", required = false) String timestamp, @RequestParam(name = "nonce", required = false) String nonce, @RequestParam(name = "echostr", required = false) String echostr) {
if (StringUtils.isAnyBlank(signature, timestamp, nonce, echostr)) {
throw new IllegalArgumentException("请求参数非法,请核实!");
}


if (wxService.checkSignature(timestamp, nonce, signature)) {
return echostr;
}

return "非法请求";
}
  1. 再写一个接口 authGet,请求方式为 Post

推送的请求包

1
2
3
4
5
6
7
<xml>
<ToUserName><![CDATA[toUser]]></ToUserName>
<FromUserName><![CDATA[FromUser]]></FromUserName>
<CreateTime>123456789</CreateTime>
<MsgType><![CDATA[event]]></MsgType>
<Event><![CDATA[subscribe]]></Event>
</xml>

用户关注/取消公众号。发送消息给公众号,都进这个方法,根据消息去处理登录逻辑即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
@PostMapping(value = "/authGet", produces = "application/xml; charset=UTF-8")
public String wxtoMessage(@RequestBody String xmlData) {

WxMpXmlMessage inMessage = WxMpXmlMessage.fromXml(xmlData);
// 处理二维码扫描事件
if ("event".equals(inMessage.getMsgType()) && ("SCAN".equals(inMessage.getEvent()) || "subscribe".equals(inMessage.getEvent()))) {
String openId = inMessage.getFromUserName(); // 获取openid
String msgType = inMessage.getMsgType(); // 获取用户的动作

//处理关注的动作
if(msgType为subscribe){//处理关注的逻辑
//1.拿到openid去判断如果用户已经注册,也可以向用户发去填写验证码的逻辑
if(用户存在){

}else{
//2.用户不存在,则注册用户,并向用户索要验证码的逻辑
}

}else{//处理取消关注的逻辑

}

return "success";
}

//判断用户从公众号发来的验证码消息进行对比,如果正确则处理登录逻辑


// 处理其他消息
return "success";
}