1.官方网站:微信登录功能 / 网页应用授权用户信息变更 (qq.com)
2.登陆流程:
和登陆微信没关系,是用户的微信号,和我程序的程序编号(微信给的 目前用的老师的),去请求微信的接口,微信给这个用户一个token,这个token在本系统中 只和该用户对应。登陆成功。
实际上有点出入,是用户扫码后确定登录后,自动去请求微信的接口,我拿不到用户的微信号。
3.具体实现
1》用户在页面点击登陆按钮,前端发送请求给后端
2》后端接收到请求,通过重定向去请求微信接口。返回重定向地址后,此时浏览器页面会自动新建一个页签,是微信的二维码登陆界面。
重定向到微信接口需要参数 appid这里使用谷粒学院老师的。
重定向地址是自己写的,在扫码之后微信会访问这个地址。
其他参数不清楚。
@GetMapping("login")
public String genQrConnect(HttpSession session) {
// 微信开放平台授权baseUrl
String baseUrl = "https://open.weixin.qq.com/connect/qrconnect" +
"?appid=%s" +
"&redirect_uri=%s" +
"&response_type=code" +
"&scope=snsapi_login" +
"&state=%s" +
"#wechat_redirect";
// 回调地址
String redirectUrl = ConstantWxUtils.REDIRECT_URL; //获取业务服务器重定向地址
try {
redirectUrl = URLEncoder.encode(redirectUrl, "UTF-8"); //url编码
} catch (UnsupportedEncodingException e) {
throw new GuLiExeception(20001, e.getMessage());
}
// 防止csrf攻击(跨站请求伪造攻击)
//String state = UUID.randomUUID().toString().replaceAll("-", "");//一般情况下会使用一个随机数
String state = "imhelen";//为了让大家能够使用我搭建的外网的微信回调跳转服务器,这里填写你在ngrok的前置域名
// 采用redis等进行缓存state 使用sessionId为key 30分钟后过期,可配置
//键:"wechar-open-state-" + httpServletRequest.getSession().getId()
//值:satte
//过期时间:30分钟
//生成qrcodeUrl
String qrcodeUrl = String.format(
baseUrl,
ConstantWxUtils.APP_ID,
redirectUrl,
state);
return "redirect:" + qrcodeUrl;
}
3.用户在扫码登陆之后,微信会访问第二步携带的回调接口,并携带这个一个临时参数,在controller中获得此参数后,携带secret,访问正式的三方登陆接口,会返回access_token,这个access_token就是 该微信用户,在本系统中,唯一的token,token和用户是一对一的关系,微信登陆完成。
获得这个token后,可以再请求一次微信用户信息接口,获取用户的头像、微信名信息。
在获取完用户的头像、微信名信息后,重定向到前端地址,以重新加载刚才的扫码页面,并且在路径中携带token,前端发现路径中有token后,获取这个token,并携带这个token访问获取用户信息接口,获取用户信息。
@GetMapping("callback")
public String callBack(String code, String state) {
//从redis中将state获取出来,和当前传入的state作比较
//如果一致则放行,如果不一致则抛出异常:非法访问
//向认证服务器发送请求换取access_token
String baseAccessTokenUrl = "https://api.weixin.qq.com/sns/oauth2/access_token" +
"?appid=%s" +
"&secret=%s" +
"&code=%s" +
"&grant_type=authorization_code";
String accessTokenUrl = String.format(baseAccessTokenUrl,
ConstantWxUtils.APP_ID,
ConstantWxUtils.APP_SECRECT,
code);
// 使用httpClient发送请求,得到返回结果
JSONObject accesstokenInfo = HttpClientUtils.httpGet(accessTokenUrl);
String access_token = (String) accesstokenInfo.get("access_token");
String openid = (String) accesstokenInfo.get("openid");
LambdaQueryWrapper lambdaQueryWrapper = new LambdaQueryWrapper();
lambdaQueryWrapper.eq(UcenterMember::getOpenid, openid);
UcenterMember checkUser = ucenterMemberService.getOne(lambdaQueryWrapper);
if (checkUser == null) {
//访问微信的资源服务器,获取用户信息
String baseUserInfoUrl = "https://api.weixin.qq.com/sns/userinfo" +
"?access_token=%s" +
"&openid=%s";
String userInfoUrl = String.format(baseUserInfoUrl, access_token, openid);
try {
JSONObject jsonObject = HttpClientUtils.httpGet(userInfoUrl);
String nickName = jsonObject.getString("nickname");
String headUrl = jsonObject.getString("headimgurl");
UcenterMember ucenterMember = new UcenterMember(openid, nickName, headUrl);
ucenterMemberService.save(ucenterMember);
String jwtToken = JwtUtils.getJwtToken(ucenterMember.getId(), nickName);
return "redirect:http://localhost:3000?guli_token=" + jwtToken;
} catch (Exception e) {
throw new GuLiExeception(20001, "用户未注册,获取腾讯用户信息失败");
}
}
String jwtToken = JwtUtils.getJwtToken(checkUser.getId(), checkUser.getNickname());
String temp=JwtUtils.getMemberIdByJwtTokenString(jwtToken);
System.out.println("=======");
System.out.println(temp);
return "redirect:http://localhost:3000?guli_token="+jwtToken;
}
前端代码, 前端发现路径中有token后,获取这个token,并携带这个token访问获取用户信息接口,获取用户信息。这是default.vue,当用户没有输入访问的具体地址时,默认访问此界面。
后端接收到前端发送的token,根据这个token查询用户信息并返回(用户信息在第三步上面已经获取到,并且保存数据库,这里只查就可以)
@GetMapping("/info")
public R geInfo(HttpServletRequest request) {
String headers = "";
Enumeration names = request.getHeaderNames();
while (names.hasMoreElements()) {
String name = names.nextElement();
String value = request.getHeader(name);
headers += name + "=" + value + "\n";
}
System.out.println(headers + "请求用户信息请求头如上");
String memberId = JwtUtils.getMemberIdByJwtToken(request);
UcenterMember uCenterMember = ucenterMemberService.getById(memberId);
return R.ok().data("member", uCenterMember);
}