使用微信测试账号实现web端扫码登录

一般网站应用使用微信扫码登录使用的是微信开放平台的接口,但注册开放平台需要提供企业信息,而且开放平台并没有提供测试账号,个人想要直接通过开放平台实现微信授权登录是不可行的。

微信在公众号中提供了测试账号,我们可以通过公众号实现微信授权;

请求过程

准备工作

1.申请微信公众号测试账号

2.设置授权回调页面地址

3.内网穿透

​ 可以使用natapp实现内网穿透

4.引入依赖

1
2
3
4
5
<!--WeChat公众号-->
<dependency>
<groupId>com.github.binarywang</groupId>
<artifactId>weixin-java-mp</artifactId>
</dependency>

代码实现

jsp页面

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<html>
<head>
<script type="text/javascript" src="${pageContext.request.contextPath}/js/jquery-1.8.3.js"></script>
<script type="text/javascript" src="${pageContext.request.contextPath}/js/jquery.qrcode.min.js"></script>
<script>
//二维码边长
var CODE_SIDE=200;
var timer;
$(function () {
reload_qrCode();
});

//重新加载二维码
function reload_qrCode() {
//清空上次生成的二维码
$("#qrcode").html("");
//清除定时器
clearInterval(timer);
$.get("${pageContext.request.contextPath}/wx/authorization?" + new Date(), function (data) {
console.info(data.url);
//生成二维码
$("#qrcode").qrcode({
render: "canvas",
width: CODE_SIDE,
height: CODE_SIDE,
text: data.url
});
add_icon("Wechat.png",72,88);
timer = setInterval(function () {
ajax_wx_login_status(data.uuid);
}, 1000);
},"json");
}
//轮询登录状态
function ajax_wx_login_status(uuid) {
$.post("${pageContext.request.contextPath}/wx/checkLogin",
{uuid: uuid},
function (res) {
if (res.status == 200) { //登录状态200,登录成功
//进行相应业务代码的编写,或者直接进行页面跳转.
//alert("登录成功");
clearInterval(timer);
window.location.href = "${pageContext.request.contextPath}/index"; //页面跳转
}if (res.status==402){
clearInterval(timer);
console.info("二维码过期");
var context = $("#qrcode canvas")[0].getContext('2d');
context.fillStyle='rgba(160, 160, 160, 0.8)';
context.fillRect(0,0,CODE_SIDE,CODE_SIDE);
context.fillStyle='rgba(0, 0, 0)';
add_icon("reload.png",48,41);
context.font="14px Arial";
context.fillText("二维码过期,请刷新",45,140);
}
}, "JSON");
}
//在二维码中间加入微信图标
function add_icon(image_name, height, width) {
var image = new Image();
image.src="${pageContext.request.contextPath}/images/"+image_name;
var context = $("#qrcode canvas")[0].getContext('2d');
$(image).load(function(){
context.drawImage(image,(CODE_SIDE-width)/2,(CODE_SIDE-height)/2);
});
}
</script>
</head>
<body>
<div id="qrcode" onclick="reload_qrCode()"></div>
</body>
</html>

controller

前端发起请求到后端,后端生成带uuid的授权链接,返回链接与uuid到前端页面,
并将uuid作为key存入redis中,前端页面生成二维码,用户扫描二维码,授权登录,微信回调当前接口
传来uuid与code,通过code再调用微信接口获得用户信息,将用户的openid或者user对象作为value保存到redis和数据库中,
前端获得二维码图片后轮询redis,查看redis中是否有user对象或openid,有则跳转,实现扫码登录

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
@Controller
@RequestMapping("/wx")
@Slf4j
@PropertySource(value = "classpath:conf/wx.properties", ignoreResourceNotFound = true)
public class WxController {

@Autowired
private WxMpService wxMpService;
@Autowired
private JedisClient jedisClient;
@Autowired
private BuyerService buyerService;
/**
* redis中保存登录用uuid的前缀
*/
private static final String REDIS_LOGIN_UUID_PRE = "LOGIN_UUID";
/**
* uuid有效时长/二维码有效期
*/
private static final int LOGIN_EXPIRE = 15;
/**
* 外网地址
*/
@Value("XXXXXXX.nat300.top")
private String URL;

@GetMapping("/portal")
@ResponseBody
public String portal(String echostr, String timestamp, String nonce, String signature) {
log.info("WeChat校验");
if (!wxMpService.checkSignature(timestamp, nonce, signature)) {
// 消息签名不正确,说明不是公众平台发过来的消息
log.warn("非法请求");
return "false";
}
if (StringUtils.isNotBlank(echostr)) {
// 说明是一个仅仅用来验证的请求,回显echostr
log.info(echostr);
return echostr;
}
log.warn("未知请求");
return null;
}

/**
* 生成用户授权链接
*/
@GetMapping(value = "/authorization", produces = "application/json;charset=UTF-8")
@ResponseBody
public String authorization() {
String url = URL + "/wx/getUserInfo";
String UUIDParam = UUID.randomUUID().toString().replace("-", "");
url = url + "/" + UUIDParam;
String authorizationUrl = wxMpService.oauth2buildAuthorizationUrl(url, WxConsts.OAuth2Scope.SNSAPI_USERINFO, null);
log.info("authorizationUrl:" + authorizationUrl);
String json = "{\"uuid\":\"" + UUIDParam + "\",\"url\":\"" + authorizationUrl + "\"}";
//将uuid存到redis,默认值为NULL
jedisClient.set(REDIS_LOGIN_UUID_PRE + ":" + UUIDParam + ":BASE", "NULL");
//设置有效时长
jedisClient.expire(REDIS_LOGIN_UUID_PRE + ":" + UUIDParam + ":BASE", LOGIN_EXPIRE);
return json;
}

/**
* 用户扫码回调的方法,获得用户信息
*
* @param code
*/
@GetMapping(value = "/getUserInfo/{uuid}")
public String getUserInfo(String code, @PathVariable String uuid, Model model) {
if (StringUtils.isBlank(uuid) || StringUtils.isBlank(code)) {
model.addAttribute("result", GMResult.build(400, "登录失败"));
return "wx/loginStats";
}
System.out.println(uuid);
String redisPre = REDIS_LOGIN_UUID_PRE + ":" + uuid + ":BASE";
log.info("code:" + code);
try {
//code只能用一次,再次使用会抛异常
WxMpOAuth2AccessToken token = wxMpService.oauth2getAccessToken(code);
WxMpUser wxMpUser = wxMpService.oauth2getUserInfo(token, null);
//检查是否存在
if (!jedisClient.exists(redisPre)) {
model.addAttribute("result", GMResult.build(402, "二维码已过期"));
return "wx/loginStats";
}
//当redis中对应uuid未被使用
if ("NULL".equals(jedisClient.get(redisPre))) {
//将用户信息保存到redis中
jedisClient.set(redisPre, JsonUtils.objectToJson(wxMpUser));
//设置有效时长
jedisClient.expire(redisPre, LOGIN_EXPIRE);
log.info(wxMpUser.toString());
model.addAttribute("result", GMResult.build(200, "登录成功"));
return "wx/loginStats";
}
} catch (WxErrorException e) {
e.printStackTrace();
model.addAttribute("result", GMResult.build(401, "请勿重复登录"));
return "wx/loginStats";
}
model.addAttribute("result", GMResult.build(403, "二维码已使用"));
System.out.println("登录失败");
return "wx/loginStats";
}


/**
* 查询用户是否授权
*
* @param uuid
* @param session
* @return
*/
@PostMapping(value = "/checkLogin", produces = "application/json;charset=UTF-8")
@ResponseBody
public GMResult checkLogin(String uuid, HttpSession session) {
String redisPre=REDIS_LOGIN_UUID_PRE + ":" + uuid + ":BASE";
//根据uuid查询redis中是否存在用户且授权
if (!jedisClient.exists(redisPre)){
return GMResult.build(402, "二维码已过期");
}
String json = jedisClient.get(redisPre);
System.out.println("等待登录。。。。。json:" + json);
if (!"NULL".equals(json)) {
//如果值不为默认值,转成user对象
WxMpUser wxMpUser = JsonUtils.jsonToPojo(json, WxMpUser.class);
System.out.println(wxMpUser.toString());
Buyer buyer=buyerService.wxLogin(wxMpUser);
//存在则跳转到登录成功页,并将user放入session中
session.setAttribute("user", buyer);
return GMResult.ok();
}
//否则返回false
return GMResult.build(400, "登录失败");
}
}