PC端微信扫码登录

发布时间 2023-03-27 11:13:51作者: 韩梦芫

微信注册应用

首先需要在微信开放平台进行注册(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端就会一直的请求服务器查询,造成资源浪费和一定的性能损耗