基于微信公众号(服务号)实现扫码自动登录系统功能
微信提供了两种方法都可以实现扫描登录。
一种是基于微信公众平台的扫码登录,另一种是基于微信开放平台的扫码登录。
两者的区别:
微信开放平台需要企业认证才能注册(认证费用300元,只需要认证1次,后续不再需要进行缴费年审)。
微信公众平台需要认证微信服务号(认证费用300元/年),才能进行扫码登录的开发。
本文主要介绍的是微信公众平台中的扫码登录。
微信公众号在线开发文档:微信公众平台开发概述 | 微信开放文档
目录
一、应用场景
我在我的网站中实现了这个功能,只需要使用微信扫描并关注公众号,即可实现自动登录。
在线体验地址:https://www.ewbang.com/community/login
首次扫码登录需要先进行关注公众号,后续只需要使用微信扫码即可完成自动登录。
注:你需要准备一个已经认证过的公众号或者测试号
二、实现原理
大致流程
1、用户访问微信快速登录页面,生成带参数的二维码。
2、监听微信推送事件(订阅或者扫码)
3、用户扫码会有两种情况:
① 用户初次关注公众号(订阅事件subscribe),根据时间返回的OpenID进行查询,如果系统中存在此用户,直接调用系统登录方法,否则创建默认账号并绑定OpenID,再调用系统登录方法。
② 用户之前关注过工作号(扫码事件SCAN),根据时间返回的OpenID进行查询,如果系统中存在此用户,直接调用系统登录方法,否则创建默认账号并绑定OpenID,再调用系统登录方法。
4、根据参数查询用户登录状态(轮询),登录成功之后,重定向到指定页面。
三、需要用到的接口
(1)获取Access token
https://developers.weixin.qq.com/doc/offiaccount/Basic_Information/Get_access_token.html
(2)生成带参数的二维码
(3)接收事件推送
https://developers.weixin.qq.com/doc/offiaccount/Message_Management/Receiving_event_pushes.html
这里只处理订阅事件和扫码事件即可。
四、核心部分代码
用到的部分依赖jar包
<!-- hutool工具包 -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.12</version>
</dependency>
<!--生成二维码依赖包-->
<dependency>
<groupId>com.google.zxing</groupId>
<artifactId>core</artifactId>
<version>3.3.3</version>
</dependency>
<!--xml解析-->
<dependency>
<groupId>dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>1.6.1</version>
</dependency>
(1)获取access_token
/**
* 获取access_token
*
* @return
*/
public String createAccessToken() {
String url = "https://api.weixin.qq.com/cgi-bin/token";
Map<String, Object> data = new HashMap<>();
data.put("grant_type", "client_credential");
data.put("appid", wxConfig.getAppid());
data.put("secret", wxConfig.getSecret());
log.info("createAccessToken data:{}", data);
String json = HttpUtil.createGet(url).form(data).execute().body();
String access_token = (String) JSONUtil.getByPath(JSONUtil.parse(json), "access_token");
return access_token;
}
(2)获取临时二维码
这里需要注意的是:
action_name:QR_SCENE(场景值ID,临时二维码时为32位非0整型,永久二维码时最大值为100000(目前参数只支持1--100000))
action_name:QR_STR_SCENE(场景值ID(字符串形式的ID),字符串类型,长度限制为1到64)
/**
* 创建临时二维码
*
* @return
*/
public String createTempQrCode(String access_token, String scene_str) {
String url = "https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token=" + access_token;
Map<String, Object> data = new HashMap<>();
data.put("expire_seconds", 604800);
data.put("action_name", "QR_STR_SCENE");
Map<String, Object> action_info = new HashMap<>();
Map<String, Object> scene = new HashMap<>();
scene.put("scene_str", scene_str);
action_info.put("scene", scene);
data.put("action_info", action_info);
String json = HttpUtil.createPost(url)
.header("Content-Type", "application/json")
.body(JSONUtil.toJsonStr(data))
.execute().body();
System.out.println("json = " + json);
String qrcode = (String) JSONUtil.getByPath(JSONUtil.parse(json), "url");
return qrcode;
}
(3)微信推送事件方法
get请求接口主要用于签名验证。(在配置开发者服务器配置(已启用)时会用到)
post请求接口主要用于事件接收逻辑处理
/*
* @param signature 微信加密签名,signature结合了开发者填写的 token 参数和请求中的 timestamp 参数、nonce参数。
* @param timestamp 时间戳
* @param nonce 这是个随机数
* @param echostr 随机字符串,验证成功后原样返回
*/
@GetMapping("/wx/event")
public void get(@RequestParam(required = false) String signature,
@RequestParam(required = false) String timestamp,
@RequestParam(required = false) String nonce,
@RequestParam(required = false) String echostr,
HttpServletResponse response) throws IOException {
response.setCharacterEncoding("UTF-8");
response.getWriter().write(echostr);
response.getWriter().flush();
response.getWriter().close();
}
//处理微信推送事件
@PostMapping("/wx/event")
public void post(final HttpServletRequest request, HttpServletResponse response) {
try {
// 微信加密签名
final String signature = request.getParameter("signature");
// 时间戳
final String timestamp = request.getParameter("timestamp");
// 随机数
final String nonce = request.getParameter("nonce");
// 随机字符串
final String echostr = request.getParameter("echostr");
//将xml文件转成易处理的map(下方贴出)
final Map<String, String> map = parseXml(request);
//开发者微信号
final String toUserName = map.get("ToUserName");
//OpenId
final String fromUserName = map.get("FromUserName");
//消息创建时间 (整型)
final String createTime = map.get("CreateTime");
//消息类型,event
final String msgType = map.get("MsgType");
//事件类型
final String event = map.get("Event");
String scene_str = "";
if ("event".equals(msgType)) {
if (event.equals("subscribe")) {
final String ticket = map.get("Ticket");
if (ticket != null) {
scene_str = map.get("EventKey").replace("qrscene_", "");
}
}
//注:事件类型为SCAN即已关注
else if (event.equals("SCAN")) {
final String ticket = map.get("Ticket");
if (ticket != null) {
scene_str = map.get("EventKey");
}
}
}
// 如果scene_str 不为空代表用户已经扫描并且关注了公众号
if (StringUtils.isNotEmpty(scene_str)) {
// 将微信公众号用户ID缓存到redis中,标记用户已经扫码完成,执行登录逻辑。
redisCache.setCacheObject(scene_str, fromUserName, LoginStatus.SEESIOIN_TIME, TimeUnit.SECONDS);
}
response.getWriter().write("");
} catch (Exception e) {
e.printStackTrace();
}
}
(4)request请求转换为Map方法
//将xml文件转成易处理的map
public static Map<String, String> parseXml(final HttpServletRequest request) throws Exception {
final Map<String, String> map = new HashMap<String, String>();
InputStream inputStream = request.getInputStream();
final SAXReader reader = new SAXReader();
final Document document = reader.read(inputStream);
final Element root = document.getRootElement();
final List<Element> elementList = (List<Element>) root.elements();
for (final Element e : elementList) {
map.put(e.getName(), e.getText());
}
inputStream.close();
inputStream = null;
return map;
}
(5)检查微信登录状态
/**
* 检查微信登录状态
*
* @return
*/
@PostMapping("/wxLogin")
@ResponseBody
public AjaxResult checkWxLoginStatus(HttpSession session) {
String scene_str = (String) session.getAttribute("scene_str");
if (StringUtils.isEmpty(scene_str)) {
return AjaxResult.custom("二维码已过期", 1001);
}
String loginStatus = redisCache.getCacheObject(scene_str);
if (LoginStatus.NOT_LOGIN.equals(loginStatus)) {
return AjaxResult.custom("未登录", 1002);
}
//执行登录方法
try {
autoWxLogin(loginStatus);
} catch (Exception e) {
return AjaxResult.error(e.getMessage());
}
return AjaxResult.success("登录成功");
}
五、你可能会遇到的问题
(1)填写服务配置可能会遇到的问题
如果配置的url不能访问,会报token验证失败。在本地开发测试,可以借助于网穿透工具:Sunny-Ngrok内网转发内网穿透 - 国内内网映射服务器
(2)本地正常,服务器上无法调用
有可能你在本地开发过程中是没有问题,但是你将代码发布到服务器上的时候,发现接口调用失败。有可能是你没有将公网服务器IP添加到白名单导致的。
多个白名单配置方法:每行配置一个IP地址即可。
六、结语
本章教程到这里就结束了,以上内容仅来自于博主的自我总结,也许其中有总结不到为的地方,希望能您够多多理解,感谢你的阅读,希望对你有一点点的帮助。