PC端微信扫码登录
微信注册应用
首先需要在微信开放平台进行注册(https://open.weixin.qq.com/),并认证一个网站应用。
注册步骤可参考:https://blog.csdn.net/qq_36014509/article/details/88996562
微信二维码API对接文档:https://developers.weixin.qq.com/doc/oplatform/Website_App/WeChat_Login/Wechat_Login.html
前端二维码展示
<div id="bodyDiv">
<button onclick="wxLogin()" class="layui-btn layui-btn-normal layui-btn-fluid" style="box-shadow: 0 1px 6px #1E9FFF;border-radius: 4px;">
<img src="${path}/img/wx.png" style="width: 18px;">
使用微信登录
</button>
</div>
页面需要引入JS文件(支持https):
http://res.wx.qq.com/connect/zh_CN/htmledition/js/wxLogin.js
1
生成微信二维码
//用的layUI的弹出框
var wxLoginSoleCodeGetTimer ;
function wxLogin(){
//背景添加高斯模糊
$("#bodyDiv").addClass("vague");
//显示微信扫码登录
layer.open({
type: 1,
closeBtn:1,
resize:false,
skin: 'wxClass',
title:['微信扫码登录'],
area: ['340px', '485px'],
content: '<div id="wx-qrcode" style="margin:20px;"></div>',
cancel: function(index, layero){
//删除背景添加高斯模糊
$("#bodyDiv").removeClass("vague");
//停止定时器
window.clearInterval(wxLoginSoleCodeGetTimer);
}
});
//调用方法生成二维码
createQRCode();
}
微信二维码进行倒计时
function createQRCode(){
//获取当前时间戳准备存入redis
var soleCode= Date.parse(new Date());
//把当前时间戳传到后台
$.get("${path}/login/wxLoginSole.do",{soleCode:soleCode},function(data){
if(data.code == "success"){
//实例微信登录二维码JS对象
var obj = new WxLogin({
self_redirect:false,
id:"wx-qrcode", //这个id为弹出框的样式id,主要是为了让生产的二维码嵌套在弹出框中
appid: "wx31bb558f766faae6",
scope: "snsapi_login",
redirect_uri: "http://localhost/login/wxLoginOAuth", //扫码后调用的后台方法
state: soleCode
})
//设置定时器每秒请求后台获取redis中时间戳做对比
wxLoginSoleCodeGetTimer = window.setInterval("getWxLoginSoleCode('"+soleCode+"')",1000);
}else{
layer.alert('出错了,请重试!', {icon: 2});
}
});
}
把时间戳存入redis
//获取前端生成的时间戳存进redis
@ResponseBody
@RequestMapping("/wxLoginSole")
public Object wxLoginSole(HttpServletRequest request,HttpServletResponse response){
Map<String,Object> result = new HashMap<String,Object>();
//取出时间戳
String soleCode = request.getParameter("soleCode");
if(StringUtils.isNotBlank(soleCode)){
//选择redis数据库1
boolean resultSetDb = redisUtil.select(1)
if(resultSetDb){
//将guid存在redis里,保存时间十分钟
boolean resultSet = redisUtil.set(soleCode ,"1",600);
if(resultSet){
result .put("code", "success");
}else{
result .put("code", "failed");
}
}else{
result .put("code", "failed");
}
}else{
result .put("code", "failed");
}
return result;
}
定时请求redis中的时间戳,看二维码是否超时
//定时请求微信登录数据
function getWxLoginSoleCode(soleCode){
$.get("${path}/login/getWxLoginSoleCode.do", {soleCode:soleCode}, function(data) {
if (data.code == 'success') {
if(data.info == 'stop'){
//二维码已过期,停止定时器且提示二维码已过期
window.clearInterval(wxLoginSoleCodeGetTimer);
//先删除微信二维码元素
$("#wx-qrcode").empty();
//自定义元素覆盖微信二维码元素
$("#wx-qrcode").append('<div style="text-align: center;padding-top: 120px;"><p>二维码已失效</p><button style="margin-top: 5px;" id="retrieve-qrcode" type="button" class="layui-btn layui-btn-normal">重新获取二维码</button></div>');
}
}else{
//后台异常或没有soleCode参数,停止定时器
window.clearInterval(wxLoginSoleCodeGetTimer);
}
});
}
//前端定时请求该方法获取redis中存入的时间戳
@ResponseBody
@RequestMapping("/getWxLoginSoleCode")
public Object getWxLoginSoleCode(HttpServletRequest request,HttpServletResponse response){
JSONObject jsonObj = new JSONObject();
try {
//取出请求中带有的时间戳
String soleCode= request.getParameter("soleCode");
if(StringUtils.isNotBlank(soleCode)){
//选择redisDb6
boolean resultSetDb = redisUtil.setDb(6);
if(resultSetDb){
//定时器每秒获取一次redis中的时间戳
String resultGet = (String)redisUtil.get(soleCode);
if(StringUtils.isBlank(resultGet)){
//当值为空已找不到时,说明二维码已到十分钟已过期,返回给前端停止定时器
jsonObj.put("info", "stop");
jsonObj.put("code", "success");
}else{
//否则继续等待
jsonObj.put("code", "success");
}
}
}else{
jsonObj.put("code", "failed");
}
} catch (Exception e) {
jsonObj.put("code", "failed");
e.printStackTrace();
}
return jsonObj;
}
//重新获取二维码
$(document).on("click", "#retrieve-qrcode", function(){
//先删除自己元素
$("#wx-qrcode").empty();
//重新加上二维码
createQRCode();
});
后端代码进行验证
返回说明
正确的返回:
{
"access_token":"ACCESS_TOKEN",
"expires_in":7200,
"refresh_token":"REFRESH_TOKEN",
"openid":"OPENID",
"scope":"SCOPE",
"unionid": "o6_bmasdasdsad6_2sgVt7hMZOPfL"
}
错误返回样例:
{"errcode":40029,"errmsg":"invalid code"}
后端登入操作
//微信登录鉴权及跳转
@ResponseBody
@RequestMapping("/wxLoginOAuth")
public Object wxLoginOAuth(HttpServletRequest request,HttpServletResponse response){
Map<String,Object> result = new HashMap<String,Object>();
// 第三方发起微信授权登录请求,微信用户允许授权第三方应用后,微信会拉起应用或重定向到第三方网站,并且带上授权临时票据code参数;
String code = request.getParameter("code");
//前端扫码登入传来的时间戳
String state = request.getParameter("state");
//用户拒绝授权时code为空,微信也不会请求该接口。为了防止非法请求,若code为空则返回
if(StringUtils.isBlank(code)){
result.put("code","failed");
result.put("msg","登入请求被拒绝!");
return result;
}
//请求state为空则不正常,直接返回。否则去redis验证是否正确合法
if(StringUtils.isBlank(state)){
result.put("code","failed");
result.put("msg","请求状态异常!");
return result;
}else{
//选择redis数据库6
boolean resultSetDb = redisUtil.setDb(6);
if(resultSetDb){
//根据state判断redis中是否存在该state。若没值说明二维码已过期或者请求不合法
boolean resultHasKey = redisUtil.hasKey(state);
//微信账号和系统账号绑定时会用到
redisUtil.set("code",code,600); //在redis缓存中保留十分钟
if(!resultHasKey){
result.put("code","failed");
result.put("msg","请求超时或异常,请重新扫码登入!");
return result;
}
}else{
result.put("code","failed");
result.put("msg","请求超时或异常,请重新扫码登入!");
return result;
}
}
//请求微信通过code获取access_token
String accessToken = getInfo("https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code");
//将请求结果字符串转json
JSONObject accessTokenJson = JSONObject.fromObject(accessToken);
String access_token = (String)accessTokenJson.get("access_token");//接口调用凭证,登录后右上角展示数据需要该值去获取
String openid = (String)accessTokenJson.get("openid");//授权用户唯一标识
String unionid = (String)accessTokenJson.get("unionid");//用户统一标识(微信登录不需要该字段,但后面如果拓展其他功能可能需要)
if(StringUtils.isNotBlank(openid) && StringUtils.isNotBlank(unionid) && StringUtils.isNotBlank(access_token)){
//跟据授权标识查询用户
User user = userService.selectByOpenid(openid);
//若根据openid查不到用户,则说明没有绑定系统账号,此时跳转到绑定账号页面。
if(user == null){
//跳转微信账号绑定页面,同时把时间戳参数state带过去
response.sendRedirect("/wxbindlogin.html?key="+state);
}else{//否则已绑定系统账号,可以直接登录
//以下为登录
User userNew = new User();
userNew.setAccount(user.getAccount());
loginService.updatePWD(userNew);
//根据access_token和openid获取用户昵称和头像用作右上角显示
String wxInfo = getInfo("https://api.weixin.qq.com/sns/userinfo?access_token="+access_token+"&openid="+openid);
//将请求结果字符串转json
JSONObject wxInfoJson = JSONObject.fromObject(wxInfo);
String nickname = (String)wxInfoJson.get("nickname");//普通用户昵称
String headimgurl = (String)wxInfoJson.get("headimgurl");//用户头像,最后一个数值代表正方形头像大小(有0、46、64、96、132数值可选,0代表640*640正方形头像),用户没有头像时该项为空
HttpSession session = request.getSession();
session.setAttribute("wxusername", nickname);
session.setAttribute("wxuserimg", headimgurl);
response.sendRedirect("/index3.jsp");
}
//不管有没有绑定账号或登录成功,state已完成鉴权任务,所以删除redis中的state信息
redisUtil.setDb(6);
redisUtil.del(state);
}else{
response.sendRedirect("/login.html");
return;
}
} catch (Exception e) {
e.printStackTrace();
try {
response.sendRedirect("/login.html");
} catch (IOException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
}
}
//调用接口
private String getInfo(String URL) {
// 创建Httpclient对象
CloseableHttpClient httpclient = HttpClients.createDefault();
CloseableHttpResponse response = null;
String resultString = null;
try {
// 创建uri
URIBuilder builder = new URIBuilder(URL);
URI uri = builder.build();
// 创建http GET请求
HttpGet httpGet = new HttpGet(uri);
// 执行请求
response = httpclient.execute(httpGet);
// 判断返回状态是否为200
if (response.getStatusLine().getStatusCode() == 200) {
resultString = EntityUtils.toString(response.getEntity(),"UTF-8");
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (response != null) {
response.close();
}
httpclient.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return resultString;
}
原文链接:https://blog.csdn.net/fffvdgjvbsfkb123456/article/details/111688135
https://blog.csdn.net/qq_36014509/article/details/88996562
扫码登录原理
扫码登录,可以分为移动端与服务端的交互和pc端与服务端的交互两个过程。
1.1 pc端和服务端
当用户打开登录页面时,页面会发起一个向服务器发送获取登录二维码的请求。
服务器接收到请求后,随机生成一个uuid(唯一标识),将这个uuid存入服务器中,同时设置一个过期时间,如果过期,需要重新获取二维码。
把uuid和校验信息生成二维码,将二维码和uuid,返回给pc页面。
pc页面拿到二维码和uuid后,发起轮询。轮询有两种情况:一种是登录成功,一种是二维码过期,过期需要提用户刷新二维码。
1.2 移动端与服务端
移动端扫码之后,会得到验证信息和uuid。因为移动端是已经登录过的,所以,本地有当前的用户信息(token、userId等),移动端将需要带上用户信息和校验信息,向服务端发送登录请求。
服务器收到请求后,对比参数中的验证信息,确定是否为用户登录请求接口,如果是,返回一个确认信息给移动端。
移动端收到返回后,将登录确认框显示给用户,用户确认是进行的登录操作后,移动端再次发送请求。
服务器拿到uuid和移动端的用户信息后,修改用户的状态,将登录成功的状态和用户信息返回给pc端。
pc端轮询得到登录成功的状态,获取登录信息,登录成功!
1.3 图解
pc端检测二维码的状态不止轮询一种方式,还有我们熟悉的socket也是可以的。
socket的方式:pc端保持着与服务器的长连接,当手机端扫描二维码后,带着解析得到的二维码ID第一次发送给服务器,当服务器收到这个请求后,代表用户已经扫描了二维码,这时服务器就可以通过socket告知PC端二维码已被扫描,等待确认;之后手机端无论是取消登录还是确认登录,都会相应的请求服务器,服务器收到请求会根据相应的逻辑处理,进而通知PC端更新相应的状态。
轮询方式:轮询方式即在PC端创建一个定时器,每隔一段时间请求服务器查询状态的更新情况,然后更新网页的显示信息。当时这个定时器得控制好启动时机和生命周期,因为PC端的二维码有可能一直没有被扫描,或者扫描之后没有下一步操作了,这时,如果没有控制好这个定时器,PC端就会一直的请求服务器查询,造成资源浪费和一定的性能损耗