微信扫描验证登录
微信掃描驗證登錄
- 前言
- 什么是OAuth2
- 微信掃碼驗證登錄思路及流程
- 碼前流程構想及細節考慮
- 實現步驟
- 實現
- 1.獲取二維碼參數信息
- 2.前端調用接口,生成二維碼
- 3.1獲取臨時票據(code)
- 3.2使用code+appid+secret換取access_token
- 3.3根據獲取的access_token進行提取獲得openid(用戶信息唯一標識)
- 3.4根據openid進行登錄
- 3.5 將用戶信息返回給前端
- 3.6登錄驗證總代碼
- 4. 前端接收參數并作校驗登錄
- 總結
前言
昨天整合了榛子云實現了短信服務驗證登錄,今天繼續實現第二種登錄方式,即微信掃碼驗證登錄~
什么是OAuth2
簡單來說,OAuth2就是一種授權認證,一種針對開放系統間授權,單點登錄和現代微服務安全的解決方案。
開放系統間授權:不同系統應用間可以通過互相訪問,而不需要重新進行登錄驗證,比如用戶需要從微信跳轉到天貓商城,這時候用戶在微信已經登錄了,但是在天貓商城中沒有登錄,沒有具體的訪問權限,這時候通過OAuth2的令牌訪問即可較好地解決此問題
單點登錄:一個模塊登錄,其它模塊就不需要進行登錄了(即一次登錄,處處訪問)
注意:OAuth2.0僅是一個授權框架,僅用于授權代理,即不同系統間的授權訪問,而這個授權是可以通過令牌實現的,正因為需要考慮如何管理令牌、頒發令牌、吊銷令牌,需要統一的協議,因此就有了OAuth2協議
微信掃碼驗證登錄思路及流程
碼前流程構想及細節考慮
上面我們簡單地了解了一下OAuth2是什么,接下來我們就理清一下如何實現微信的掃碼驗證登錄
我們體驗過掃碼登錄流程,大致流程如下:
待掃描- - ->已掃描,待確認- - ->已確認
其實其中還有非常多細節需要我們注意:
當然啦,我這里就沒有考慮到細節因素,這部分由于自己申請不下來就使用的是尚硅谷提供的二維碼掃描,也就沒有唯一綁定
實現步驟
- 使用方法生成微信掃碼的二維碼
- 返回生成二維碼所需要的參數給前端
- 前端拿到參數,然后在前端中調用遠程接口獲取二維碼,頁面跳轉
- 掃描二維碼,手機確認登錄
- 手機確認登錄后,微信的服務器調用回調地址,跳轉到本地方法接口中(回調本地方法),回調時傳遞code【臨時token】和state【狀態】,然后在本地方法中
- 第三方應用獲取到接口調用憑證,微信服務器校驗
- 校驗無誤,然后可以獲取到用戶個人信息,根據個人信息進行登錄驗證即可
圖片來源于B站拓薪教育
實現
1.獲取二維碼參數信息
/** 獲取微信登錄參數** @param session* @return* @throws UnsupportedEncodingException** 1. 該方法生成微信掃描的二維碼* 2. 返回生成二維碼所需要的參數*/@GetMapping("getLoginParam")@ResponseBody //通過這個注解可以返回數據public Result genQrConnect(HttpSession session) throws UnsupportedEncodingException {Map<String,Object> map = new HashMap<>();String redirectUri = URLEncoder.encode(ConstantWxPropertiesUtils.WX_OPEN_REDIRECT_URL, "UTF-8");//必須map.put("appid", ConstantWxPropertiesUtils.WX_OPEN_APP_ID);map.put("redirectUri",redirectUri);map.put("scope", "snsapi_login");//非必須map.put("state",System.currentTimeMillis()+"");return Result.ok(map);}2.前端調用接口,生成二維碼
weixinLogin() {this.dialogAtrr.showLoginType = 'weixin'weixinApi.getLoginParam().then(response => {var obj = new WxLogin({self_redirect:true,id: 'weixinLogin', // 需要顯示的容器idappid: response.data.appid, // 公眾號appid wx*******scope: response.data.scope, // 網頁默認即可redirect_uri: response.data.redirectUri, // 授權成功后回調的urlstate: response.data.state, // 可設置為簡單的隨機數加session用來校驗style: 'black', // 提供"black"、"white"可選。二維碼的樣式href: '' // 外部css文件url,需要https})})},3.1獲取臨時票據(code)
//1.獲取臨時票據code[攔截非法回調]if (StringUtils.isBlank(state) || StringUtils.isBlank(code)) {log.error("非法回調請求");throw new YyghException(ResultCodeEnum.ILLEGAL_CALLBACK_REQUEST_ERROR);}3.2使用code+appid+secret換取access_token
//2.使用code和appid以及appscrect換取access_tokenStringBuffer baseAccessTokenUrl = new StringBuffer().append("https://api.weixin.qq.com/sns/oauth2/access_token").append("?appid=%s").append("&secret=%s").append("&code=%s").append("&grant_type=authorization_code");String accessTokenUrl = String.format(baseAccessTokenUrl.toString(),ConstantWxPropertiesUtils.WX_OPEN_APP_ID,ConstantWxPropertiesUtils.WX_OPEN_APP_SECRET,code);//3.根據HttpClientUtils進行http請求回調String result = null;try {result = HttpClientUtils.get(accessTokenUrl);} catch (Exception e) {throw new YyghException(ResultCodeEnum.FETCH_ACCESSTOKEN_FAILD);}System.out.println("使用code換取的access_token結果 = " + result);3.3根據獲取的access_token進行提取獲得openid(用戶信息唯一標識)
JSONObject resultJson = JSONObject.parseObject(result);if(resultJson.getString("errcode") != null){log.error("獲取access_token失敗:" + resultJson.getString("errcode") + resultJson.getString("errmsg"));throw new YyghException(ResultCodeEnum.FETCH_ACCESSTOKEN_FAILD);}String accessToken = resultJson.getString("access_token");String openId = resultJson.getString("openid");log.info(accessToken);log.info(openId);3.4根據openid進行登錄
//判斷數據庫中是否存在掃碼人的信息(根據openid進行唯一標識判斷)UserInfo userInfo = userInfoService.selectWxInfoOpenId(openId);if(userInfo == null){ //數據庫中不存在信息,先對用戶信息進行存儲//4.拿著openid和access_token請求微信地址,得到掃描人信息//具體步驟://根據access_token獲取微信用戶的基本信息//先根據openid進行數據庫查詢// UserInfo userInfo = userInfoService.getByOpenid(openId);// 如果沒有查到用戶信息,那么調用微信個人信息獲取的接口// if(null == userInfo){//如果查詢到個人信息,那么直接進行登錄//使用access_token換取受保護的資源:微信的個人信息String baseUserInfoUrl = "https://api.weixin.qq.com/sns/userinfo" +"?access_token=%s" +"&openid=%s";String userInfoUrl = String.format(baseUserInfoUrl, accessToken, openId);String resultUserInfo = null;try {resultUserInfo = HttpClientUtils.get(userInfoUrl);} catch (Exception e) {throw new YyghException(ResultCodeEnum.FETCH_USERINFO_ERROR);}System.out.println("使用access_token獲取用戶信息的結果 = " + resultUserInfo);JSONObject resultUserInfoJson = JSONObject.parseObject(resultUserInfo);if(resultUserInfoJson.getString("errcode") != null){log.error("獲取用戶信息失敗:" + resultUserInfoJson.getString("errcode") + resultUserInfoJson.getString("errmsg"));throw new YyghException(ResultCodeEnum.FETCH_USERINFO_ERROR);}//5.解析用戶信息(用戶昵稱和用戶頭像)String nickname = resultUserInfoJson.getString("nickname");String headimgurl = resultUserInfoJson.getString("headimgurl");//添加到數據庫userInfo = new UserInfo();userInfo.setOpenid(openId);userInfo.setNickName(nickname);userInfo.setStatus(1);userInfoService.save(userInfo);}//6.將獲取到的掃碼人的信息添加到數據庫Map<String, Object> map = new HashMap<>();String name = userInfo.getName();if(StringUtils.isBlank(name)) {name = userInfo.getNickName();}if(StringUtils.isBlank(name)) {name = userInfo.getPhone();}map.put("name", name);//判斷手機號是否為空,如果手機號為空,那么返回openid,否則返回手機號if(StringUtils.isBlank(userInfo.getPhone())) {map.put("openid", userInfo.getOpenid());} else {map.put("openid", "");}String token = JwtHelper.createToken(userInfo.getId(), name);map.put("token", token);3.5 將用戶信息返回給前端
return "redirect:"+ ConstantWxPropertiesUtils.YYGH_BASE_URL+ "/weixin/callback?token="+map.get("token")+"&openid="+map.get("openid")+"&name="+ URLEncoder.encode((String)map.get("name"),"utf-8");3.6登錄驗證總代碼
@GetMapping("callback")public String callback(String code,String state) throws UnsupportedEncodingException {//1.獲取臨時票據code[攔截非法回調]if (StringUtils.isBlank(state) || StringUtils.isBlank(code)) {log.error("非法回調請求");throw new YyghException(ResultCodeEnum.ILLEGAL_CALLBACK_REQUEST_ERROR);}//2.使用code和appid以及appscrect換取access_tokenStringBuffer baseAccessTokenUrl = new StringBuffer().append("https://api.weixin.qq.com/sns/oauth2/access_token").append("?appid=%s").append("&secret=%s").append("&code=%s").append("&grant_type=authorization_code");String accessTokenUrl = String.format(baseAccessTokenUrl.toString(),ConstantWxPropertiesUtils.WX_OPEN_APP_ID,ConstantWxPropertiesUtils.WX_OPEN_APP_SECRET,code);//3.根據HttpClientUtils進行http請求回調String result = null;try {result = HttpClientUtils.get(accessTokenUrl);} catch (Exception e) {throw new YyghException(ResultCodeEnum.FETCH_ACCESSTOKEN_FAILD);}System.out.println("使用code換取的access_token結果 = " + result);JSONObject resultJson = JSONObject.parseObject(result);if(resultJson.getString("errcode") != null){log.error("獲取access_token失敗:" + resultJson.getString("errcode") + resultJson.getString("errmsg"));throw new YyghException(ResultCodeEnum.FETCH_ACCESSTOKEN_FAILD);}String accessToken = resultJson.getString("access_token");String openId = resultJson.getString("openid");log.info(accessToken);log.info(openId);//判斷數據庫中是否存在掃碼人的信息(根據openid進行唯一標識判斷)UserInfo userInfo = userInfoService.selectWxInfoOpenId(openId);if(userInfo == null){ //數據庫中不存在信息,先對用戶信息進行存儲//4.拿著openid和access_token請求微信地址,得到掃描人信息//具體步驟://根據access_token獲取微信用戶的基本信息//先根據openid進行數據庫查詢// UserInfo userInfo = userInfoService.getByOpenid(openId);// 如果沒有查到用戶信息,那么調用微信個人信息獲取的接口// if(null == userInfo){//如果查詢到個人信息,那么直接進行登錄//使用access_token換取受保護的資源:微信的個人信息String baseUserInfoUrl = "https://api.weixin.qq.com/sns/userinfo" +"?access_token=%s" +"&openid=%s";String userInfoUrl = String.format(baseUserInfoUrl, accessToken, openId);String resultUserInfo = null;try {resultUserInfo = HttpClientUtils.get(userInfoUrl);} catch (Exception e) {throw new YyghException(ResultCodeEnum.FETCH_USERINFO_ERROR);}System.out.println("使用access_token獲取用戶信息的結果 = " + resultUserInfo);JSONObject resultUserInfoJson = JSONObject.parseObject(resultUserInfo);if(resultUserInfoJson.getString("errcode") != null){log.error("獲取用戶信息失敗:" + resultUserInfoJson.getString("errcode") + resultUserInfoJson.getString("errmsg"));throw new YyghException(ResultCodeEnum.FETCH_USERINFO_ERROR);}//5.解析用戶信息(用戶昵稱和用戶頭像)String nickname = resultUserInfoJson.getString("nickname");String headimgurl = resultUserInfoJson.getString("headimgurl");//添加到數據庫userInfo = new UserInfo();userInfo.setOpenid(openId);userInfo.setNickName(nickname);userInfo.setStatus(1);userInfoService.save(userInfo);}//6.將獲取到的掃碼人的信息添加到數據庫Map<String, Object> map = new HashMap<>();String name = userInfo.getName();if(StringUtils.isBlank(name)) {name = userInfo.getNickName();}if(StringUtils.isBlank(name)) {name = userInfo.getPhone();}map.put("name", name);//判斷手機號是否為空,如果手機號為空,那么返回openid,否則返回手機號if(StringUtils.isBlank(userInfo.getPhone())) {map.put("openid", userInfo.getOpenid());} else {map.put("openid", "");}String token = JwtHelper.createToken(userInfo.getId(), name);map.put("token", token);return "redirect:"+ ConstantWxPropertiesUtils.YYGH_BASE_URL+ "/weixin/callback?token="+map.get("token")+"&openid="+map.get("openid")+"&name="+ URLEncoder.encode((String)map.get("name"),"utf-8");}4. 前端接收參數并作校驗登錄
callback.vue
<template><!-- header --><div></div><!-- footer --> </template> <script> export default {layout: "empty",data() {return {}},mounted() {let token = this.$route.query.tokenlet name = this.$route.query.namelet openid = this.$route.query.openid// 調用父vue方法window.parent['loginCallback'](name, token, openid)} } </script>myheader.vue
loginCallback(name, token, openid) {// 打開手機登錄層,綁定手機號,改邏輯與手機登錄一致if(openid != '') {this.userInfo.openid = openidthis.showLogin()} else {this.setCookies(name, token)}},總結
將微信登錄流程搞清楚才能在代碼實戰中清楚,這方面還有待加強~
總結
- 上一篇: OSI网络七层模型
- 下一篇: [Luogu] P1939 【模板】矩阵