微信小程序使用uni-app和springboot實現(xiàn)一鍵登錄功能(JWT鑒權(quán))
概述
本篇博本主要為了記錄使用uni-app開發(fā)微信小程序時實現(xiàn)微信一鍵登錄功能,并且使用JWT實現(xiàn)身份認證。
微信登錄接口說明
可以點擊==>官方的登錄時序圖<== ,看到官方描述登錄的流程。
大概描述就是 :uni-app調(diào)用login()方法,獲取臨時憑證code,將此臨時憑證發(fā)送到我們的后端,后端通過我們傳入的臨時憑證code,調(diào)用微信接口服務(wù)獲取當(dāng)前微信用戶的唯一標識openid,我們就可以憑借此openid知道哪一個用戶在進行登錄操作。
值得注意的是:
1.通過login()獲取的臨時憑證code的有效期為5分鐘,并且只能使用一次。
2.后端調(diào)用微信憑證驗證接口獲取openid需要appId和appSecret,兩者都可以到微信小程序官網(wǎng)==>開發(fā)管理==>開發(fā)設(shè)置 中獲取。
如下是我畫的整體大概流程
總體說明 :整個流程就是當(dāng)用戶點擊"微信一鍵登錄",傳入臨時憑證code,后端通過臨時憑證code去微信服務(wù)接口獲取該用戶的openid,此openid是唯一的不會變的。 那么我們就可以將openid存儲用戶數(shù)據(jù)表中,用來標識此用戶。
關(guān)于獲取微信用戶的信息
關(guān)于API:uni.getUserInfo(OBJECT) 和 uni.getUserProfile(OBJECT) 接口的說明。
目前兩個接口都無法獲取用戶信息,只能獲取到默認用戶信息,名稱為'微信用戶',頭像為灰色用戶頭像。
關(guān)于官方描述:==>原文<==
那么解決方案只能是用戶登錄后,讓用戶自行上傳修改信息。
前端代碼(uni-app)
前端的代碼很簡單,只是調(diào)用uni.login()獲取臨時憑證code傳入后端接口即可。
<template> <view> <button id="loginHanle" @tap="goLogin">微信一鍵登錄</button> </view> </template> <script> export default { methods: { // 登錄按鈕觸發(fā) loginHanle() { // 獲取臨時登錄憑證code。 uni.login({ provider: 'weixin', success(res) { console.log(res.code); // 調(diào)用后端接口,傳入code axios.post('http://localhost:8888/api/userInfo/login',{code:res.code}) .then(res=>{ // 登錄成功后的邏輯處理 ... }) } }) } } </script>
后端代碼(SpringBoot)
后端需要接收前端傳入的臨時憑證code,向微信服務(wù)器發(fā)送請求獲取登錄用戶的openid。并且操作數(shù)據(jù)庫后返回用戶信息,以及響應(yīng)頭返回token。
配置文件:application.yml
# JWT配置 jwt: header: "Authorization" #token返回頭部 tokenPrefix: "Bearer " #token前綴 secret: "maohe101" #密鑰 expireTime: 3600000 #token有效時間 3600000毫秒 ==> 60分鐘 # 微信小程序配置碼 APPID: 自己的appid APPSECRET: 自己的密匙
配置文件:Pom.xml
添加需要依賴
<!-- jwt支持 --> <dependency> <groupId>com.auth0</groupId> <artifactId>java-jwt</artifactId> <version>3.19.2</version> </dependency> <!-- json格式化 --> <dependency> <groupId>com.google.code.gson</groupId> <artifactId>gson</artifactId> <version>2.8.9</version> </dependency>
類:WeChatModel
接收前端傳入?yún)?shù)
package com.mh.common; import lombok.Data; /** * Date:2023/5/24 * author:zmh * description: 接收小程序傳入?yún)?shù) **/ @Data public class WeChatModel { /** * 臨時登錄憑證 */ private String code; /** * 微信服務(wù)器上的唯一id */ private String openId; }
類:WeChatSessionModel
接收調(diào)用微信驗證code后返回的數(shù)據(jù)。
package com.mh.common; import lombok.Data; /** * Date:2023/5/24 * author:zmh * description: 接收微信服務(wù)器返回參數(shù) **/ @Data public class WeChatSessionModel { /** * 微信服務(wù)器上辨識用戶的唯一id */ private String openid; /** * 身份憑證 */ private String session_key; /** * 錯誤代碼 */ private String errcode; /** * 錯誤信息 */ private String errmsg; }
類:UserInfoController
接收臨時憑證code,調(diào)用業(yè)務(wù)層方法
@Autowired private UserInfoService userInfoService; /** * 微信登錄 * @param weChatModel 獲取臨時憑證code * @param response · * @return 返回執(zhí)行結(jié)果 */ @PostMapping("/login") public R<String> loginCheck(@RequestBody WeChatModel weChatModel, HttpServletResponse response){ // 檢查登錄 Map<String, Object> resultMap = userInfoService.checkLogin(weChatModel.getCode()); // resultMap大于1為通過,業(yè)務(wù)層判斷正確后返回用戶信息和token,所以應(yīng)該size為2才正確。 if (resultMap.size() > 1){ log.info("創(chuàng)建的token為=>{}", resultMap.get("token")); // 將token添加入響應(yīng)頭以及返回用戶信息 response.setHeader(JWTUtils.header, (String) resultMap.get("token")); return R.success(resultMap.get("user").toString()); }else{ // 當(dāng)返回map的size為1時,即為報錯信息 return R.error(resultMap.get("errmsg").toString()); } }
業(yè)務(wù)層實現(xiàn)類:UserInfoServiceImpl
登錄驗證的邏輯處理
package com.mh.service.impl; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.google.gson.Gson; import com.mh.common.R; import com.mh.common.WeChatSessionModel; import com.mh.common.WeChatModel; import com.mh.dao.FansInfoDao; import com.mh.dao.FollowInfoDao; import com.mh.dao.UserInfoDao; import com.mh.pojo.FansInfo; import com.mh.pojo.FollowInfo; import com.mh.pojo.UserInfo; import com.mh.service.UserInfoService; import com.mh.utils.JWTUtils; import lombok.extern.slf4j.Slf4j; import org.apache.catalina.User; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Service; import org.springframework.web.client.RestTemplate; import java.util.HashMap; import java.util.Map; import java.util.UUID; /** * Date:2023/5/24 * author:zmh * description: 用戶信息業(yè)務(wù)層實現(xiàn)類 **/ @Service @Slf4j public class UserInfoServiceImpl extends ServiceImpl<UserInfoDao, UserInfo> implements UserInfoService { @Autowired private UserInfoDao userInfoDao; @Value("${APPID}") private String appid; @Value("${APPSECRET}") private String appsecret; @Autowired private RestTemplate restTemplate; // 用于存儲用戶信息和token Map<String,Object> map = new HashMap<>(); /** * 登錄驗證 * @param code 臨時登錄碼 * @return · */ public Map<String,Object> checkLogin(String code){ // 根據(jù)傳入code,調(diào)用微信服務(wù)器,獲取唯一openid // 微信服務(wù)器接口地址 String url = "https://api.weixin.qq.com/sns/jscode2session?appid="+appid+ "&secret="+appsecret +"&js_code="+ code +"&grant_type=authorization_code"; String errmsg = ""; String errcode = ""; String session_key = ""; String openid = ""; WeChatSessionModel weChatSessionModel; // 發(fā)送請求 ResponseEntity<String> responseEntity = restTemplate.exchange(url, HttpMethod.GET, null, String.class); // 判斷請求是否成功 if(responseEntity != null && responseEntity.getStatusCode() == HttpStatus.OK) { // 獲取主要內(nèi)容 String sessionData = responseEntity.getBody(); Gson gson = new Gson(); //將json字符串轉(zhuǎn)化為實體類; weChatSessionModel = gson.fromJson(sessionData, WeChatSessionModel.class); log.info("返回的數(shù)據(jù)==>{}",weChatSessionModel); //獲取用戶的唯一標識openid openid = weChatSessionModel.getOpenid(); //獲取錯誤碼 errcode = weChatSessionModel.getErrcode(); //獲取錯誤信息 errmsg = weChatSessionModel.getErrmsg(); }else{ log.info("出現(xiàn)錯誤,錯誤信息:{}",errmsg ); map.put("errmsg",errmsg); return map; } // 判斷是否成功獲取到openid if ("".equals(openid) || openid == null){ log.info("錯誤獲取openid,錯誤信息:{}",errmsg); map.put("errmsg",errmsg); return map; }else{ // 判斷用戶是否存在,查詢數(shù)據(jù)庫 LambdaQueryWrapper<UserInfo> queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.eq(UserInfo::getOpenid, openid); UserInfo userInfo = userInfoDao.selectOne(queryWrapper); // 不存在,加入數(shù)據(jù)表 if (userInfo == null){ // 填充初始信息 UserInfo tempUserInfo = new UserInfo(UUID.randomUUID().toString(), openid, "微信用戶", 1,"default.png", "",0,0,0); // 加入數(shù)據(jù)表 userInfoDao.insert(tempUserInfo); // 加入map返回 map.put("user",tempUserInfo); // 調(diào)用自定義類封裝的方法,創(chuàng)建token String token = JWTUtils.createToken(tempUserInfo.getId().toString()); map.put("token",token); return map; }else{ // 存在,將用戶信息加入map返回 map.put("user",userInfo); String token = JWTUtils.createToken(userInfo.getId().toString()); map.put("token",token); return map; } } } }
工具類:JWTUtils
用于創(chuàng)建,驗證和更新token
package com.mh.utils; import com.auth0.jwt.JWT; import com.auth0.jwt.algorithms.Algorithm; import com.auth0.jwt.exceptions.TokenExpiredException; import lombok.Data; import lombok.extern.slf4j.Slf4j; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; import java.util.Date; /** * Date:2023/5/24 * author:zmh * description:JWT工具類,JWT生成,驗證 **/ @Component @Data @ConfigurationProperties(prefix = "jwt") @Slf4j public class JWTUtils { //定義token返回頭部 public static String header; //token前綴 public static String tokenPrefix; //簽名密鑰 public static String secret; //有效期 public static long expireTime; public void setHeader(String header) { JWTUtils.header = header; } public void setTokenPrefix(String tokenPrefix) { JWTUtils.tokenPrefix = tokenPrefix; } public void setSecret(String secret) { JWTUtils.secret = secret; } public void setExpireTime(long expireTime) { JWTUtils.expireTime = expireTime; } /** * 創(chuàng)建TOKEN * * @param sub * @return */ public static String createToken(String sub) { return tokenPrefix + JWT.create() .withSubject(sub) .withExpiresAt(new Date(System.currentTimeMillis() + expireTime)) .sign(Algorithm.HMAC512(secret)); } /** * 驗證token * * @param token */ public static String validateToken(String token) { try { return JWT.require(Algorithm.HMAC512(secret)) .build() .verify(token.replace(tokenPrefix, "")) .getSubject(); } catch (TokenExpiredException e) { log.info("token已過期"); return ""; } catch (Exception e) { log.info("token驗證失敗"); return ""; } } /** * 檢查token是否需要更新 * @param token · * @return */ public static boolean isNeedUpdate(String token) { //獲取token過期時間 Date expiresAt = null; try { expiresAt = JWT.require(Algorithm.HMAC512(secret)) .build() .verify(token.replace(tokenPrefix, "")) .getExpiresAt(); } catch (TokenExpiredException e) { return true; } catch (Exception e) { log.info("token驗證失敗"); return false; } //如果剩余過期時間少于過期時常的一般時 需要更新 return (expiresAt.getTime() - System.currentTimeMillis()) < (expireTime >> 1); } }
攔截器配置-自定義攔截器
當(dāng)用戶訪問非登錄接口時,需要攔截請求,判斷用戶的請求頭是否攜帶了正確的token,攜帶了代表登錄過了,請求通過,返回數(shù)據(jù),若未token驗證失敗則錯誤提示。
package com.mh.utils; import lombok.extern.slf4j.Slf4j; import org.springframework.web.servlet.HandlerInterceptor; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * Date:2023/5/26 * author:zmh * description: 自定義登錄攔截器 **/ @Slf4j public class UserLoginInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 獲取請求頭,header值為Authorization,承載token String token = request.getHeader(JWTUtils.header); //token不存在 if (token == null || token.equals("")) { log.info("傳入token為空"); response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Token為空!"); return false; } //驗證token String sub = JWTUtils.validateToken(token); if (sub == null || sub.equals("")){ log.info("token驗證失敗"); response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Token驗證失敗!"); return false; } //更新token有效時間 (如果需要更新其實就是產(chǎn)生一個新的token) if (JWTUtils.isNeedUpdate(token)){ String newToken = JWTUtils.createToken(sub); response.setHeader(JWTUtils.header,newToken); } return true; } }
攔截器配置-注冊自定義攔截器
package com.mh.config; import com.mh.utils.UserLoginInterceptor; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; /** * Date:2023/5/26 * author:zmh * description: MVW配置 **/ @Configuration public class WebMvcConfig implements WebMvcConfigurer { /** * 注冊自定義攔截器 * @param registry · */ @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new UserLoginInterceptor()) .addPathPatterns("/api/**") // 攔截地址 .excludePathPatterns("/api/userInfo/login");// 開放登錄路徑 } }
測試(Postman)
1.測試微信一鍵登錄
微信小程序獲取臨時憑證code
通過返回的code到postman中測試調(diào)用后端登錄接口
獲取到返回,代表登錄成功。
2.測試token的驗證
調(diào)用非登錄接口,會被攔截進行token的檢查。
后端日志輸出:
攜帶錯誤或過期的token,驗證失敗
后端日志輸出
攜帶正確且在有效期內(nèi)的token,驗證成功,測試通過。
總結(jié)
對于如上代碼,其實微信登錄的邏輯是比較簡單的,代碼更多的是在處理身份驗證(token驗證),后端設(shè)置了請求攔截器,會去攔截所有非登錄接口,通過檢查token判斷是否登錄過了。
對于前端發(fā)送請求,如上只是使用了Postman進行接口的訪問,并沒有從代碼層面去發(fā)送請求,那么,其實前端是比較需要去封裝請求方法的,在封裝的請求方法中加入請求頭攜帶token,避免每一次請求都需要手動加上請求頭攜帶token。
到此這篇關(guān)于微信小程序使用uni-app和springboot實現(xiàn)一鍵登錄功能的文章就介紹到這了,更多相關(guān)微信小程序一鍵登錄功能內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
js實現(xiàn)改進的仿藍色論壇導(dǎo)航菜單效果代碼
這篇文章主要介紹了js實現(xiàn)改進的仿藍色論壇導(dǎo)航菜單效果代碼,涉及JavaScript頁面元素的遍歷及樣式動態(tài)變換技巧,具有一定參考借鑒價值,需要的朋友可以參考下2015-09-09微信小程序使用for循環(huán)動態(tài)渲染頁面操作示例
這篇文章主要介紹了微信小程序使用for循環(huán)動態(tài)渲染頁面操作,結(jié)合實例形式分析了微信小程序使用for語句獲取data數(shù)據(jù)渲染頁面相關(guān)操作技巧,需要的朋友可以參考下2018-12-12js字符串截取函數(shù)substr substring slice使用對比
字符串截取函數(shù)有substr、substring以及slice等等,下面將為大家介紹下各自的使用,感興趣的朋友可以了解下2013-11-11JavaScript控制語句及搭建前端服務(wù)器的過程詳解
這篇文章主要介紹了JavaScript控制語句及搭建前端服務(wù)器,本文給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2023-04-04