目錄
概述
微信登錄接口說(shuō)明
?關(guān)于獲取微信用戶的信息
前端代碼(uni-app)
后端代碼(SpringBoot)
配置文件:application.yml?
配置文件:Pom.xml?
類:WeChatModel??
?類:WeChatSessionModel
?類:UserInfoController
業(yè)務(wù)層實(shí)現(xiàn)類:UserInfoServiceImpl
工具類:JWTUtils
攔截器配置-自定義攔截器
攔截器配置-注冊(cè)自定義攔截器
測(cè)試(Postman)
總結(jié)
概述
本篇博本主要為了記錄使用uni-app開(kāi)發(fā)微信小程序時(shí)實(shí)現(xiàn)微信一鍵登錄功能,并且使用JWT實(shí)現(xiàn)身份認(rèn)證。
微信登錄接口說(shuō)明
可以點(diǎn)擊==>官方的登錄時(shí)序圖<== ,看到官方描述登錄的流程。
大概描述就是?:uni-app調(diào)用login()方法,獲取臨時(shí)憑證code,將此臨時(shí)憑證發(fā)送到我們的后端,后端通過(guò)我們傳入的臨時(shí)憑證code,調(diào)用微信接口服務(wù)獲取當(dāng)前微信用戶的唯一標(biāo)識(shí)openid,我們就可以憑借此openid知道哪一個(gè)用戶在進(jìn)行登錄操作。
值得注意的是:
1.通過(guò)login()獲取的臨時(shí)憑證code的有效期為5分鐘,并且只能使用一次。
2.后端調(diào)用微信憑證驗(yàn)證接口獲取openid需要appId和appSecret,兩者都可以到微信小程序官網(wǎng)==>開(kāi)發(fā)管理==>開(kāi)發(fā)設(shè)置 中獲取。
如下是我畫的整體大概流程
總體說(shuō)明?:整個(gè)流程就是當(dāng)用戶點(diǎn)擊"微信一鍵登錄",傳入臨時(shí)憑證code,后端通過(guò)臨時(shí)憑證code去微信服務(wù)接口獲取該用戶的openid,此openid是唯一的不會(huì)變的。?那么我們就可以將openid存儲(chǔ)用戶數(shù)據(jù)表中,用來(lái)標(biāo)識(shí)此用戶。
?關(guān)于獲取微信用戶的信息
關(guān)于API:uni.getUserInfo(OBJECT)?和?uni.getUserProfile(OBJECT) 接口的說(shuō)明。
目前兩個(gè)接口都無(wú)法獲取用戶信息,只能獲取到默認(rèn)用戶信息,名稱為'微信用戶',頭像為灰色用戶頭像。
關(guān)于官方描述:==>原文<==
?那么解決方案只能是用戶登錄后,讓用戶自行上傳修改信息。
前端代碼(uni-app)
?前端的代碼很簡(jiǎn)單,只是調(diào)用uni.login()獲取臨時(shí)憑證code傳入后端接口即可。
<template>
<view>
<button id="loginHanle" @tap="goLogin">微信一鍵登錄</button>
</view>
</template>
<script>
export default {
methods: {
// 登錄按鈕觸發(fā)
loginHanle() {
// 獲取臨時(shí)登錄憑證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)
后端需要接收前端傳入的臨時(shí)憑證code,向微信服務(wù)器發(fā)送請(qǐng)求獲取登錄用戶的openid。并且操作數(shù)據(jù)庫(kù)后返回用戶信息,以及響應(yīng)頭返回token。
配置文件:application.yml?
# JWT配置
jwt:
header: "Authorization" #token返回頭部
tokenPrefix: "Bearer " #token前綴
secret: "maohe101" #密鑰
expireTime: 3600000 #token有效時(shí)間 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 {
/**
* 臨時(shí)登錄憑證
*/
private String code;
/**
* 微信服務(wù)器上的唯一id
*/
private String openId;
}
?類:WeChatSessionModel
接收調(diào)用微信驗(yàn)證code后返回的數(shù)據(jù)。
package com.mh.common;
import lombok.Data;
/**
* Date:2023/5/24
* author:zmh
* description: 接收微信服務(wù)器返回參數(shù)
**/
@Data
public class WeChatSessionModel {
/**
* 微信服務(wù)器上辨識(shí)用戶的唯一id
*/
private String openid;
/**
* 身份憑證
*/
private String session_key;
/**
* 錯(cuò)誤代碼
*/
private String errcode;
/**
* 錯(cuò)誤信息
*/
private String errmsg;
}
?類:UserInfoController
接收臨時(shí)憑證code,調(diào)用業(yè)務(wù)層方法
@Autowired
private UserInfoService userInfoService;
/**
* 微信登錄
* @param weChatModel 獲取臨時(shí)憑證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為通過(guò),業(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時(shí),即為報(bào)錯(cuò)信息
return R.error(resultMap.get("errmsg").toString());
}
}
業(yè)務(wù)層實(shí)現(xiàn)類:UserInfoServiceImpl
登錄驗(yàn)證的邏輯處理?
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ù)層實(shí)現(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;
// 用于存儲(chǔ)用戶信息和token
Map<String,Object> map = new HashMap<>();
/**
* 登錄驗(yàn)證
* @param code 臨時(shí)登錄碼
* @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ā)送請(qǐng)求
ResponseEntity<String> responseEntity = restTemplate.exchange(url, HttpMethod.GET, null, String.class);
// 判斷請(qǐng)求是否成功
if(responseEntity != null && responseEntity.getStatusCode() == HttpStatus.OK) {
// 獲取主要內(nèi)容
String sessionData = responseEntity.getBody();
Gson gson = new Gson();
//將json字符串轉(zhuǎn)化為實(shí)體類;
weChatSessionModel = gson.fromJson(sessionData, WeChatSessionModel.class);
log.info("返回的數(shù)據(jù)==>{}",weChatSessionModel);
//獲取用戶的唯一標(biāo)識(shí)openid
openid = weChatSessionModel.getOpenid();
//獲取錯(cuò)誤碼
errcode = weChatSessionModel.getErrcode();
//獲取錯(cuò)誤信息
errmsg = weChatSessionModel.getErrmsg();
}else{
log.info("出現(xiàn)錯(cuò)誤,錯(cuò)誤信息:{}",errmsg );
map.put("errmsg",errmsg);
return map;
}
// 判斷是否成功獲取到openid
if ("".equals(openid) || openid == null){
log.info("錯(cuò)誤獲取openid,錯(cuò)誤信息:{}",errmsg);
map.put("errmsg",errmsg);
return map;
}else{
// 判斷用戶是否存在,查詢數(shù)據(jù)庫(kù)
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)建,驗(yàn)證和更新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生成,驗(yàn)證
**/
@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));
}
/**
* 驗(yàn)證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已過(guò)期");
return "";
} catch (Exception e) {
log.info("token驗(yàn)證失敗");
return "";
}
}
/**
* 檢查token是否需要更新
* @param token ·
* @return
*/
public static boolean isNeedUpdate(String token) {
//獲取token過(guò)期時(shí)間
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驗(yàn)證失敗");
return false;
}
//如果剩余過(guò)期時(shí)間少于過(guò)期時(shí)常的一般時(shí) 需要更新
return (expiresAt.getTime() - System.currentTimeMillis()) < (expireTime >> 1);
}
}
攔截器配置-自定義攔截器
當(dāng)用戶訪問(wèn)非登錄接口時(shí),需要攔截請(qǐng)求,判斷用戶的請(qǐng)求頭是否攜帶了正確的token,攜帶了代表登錄過(guò)了,請(qǐng)求通過(guò),返回?cái)?shù)據(jù),若未token驗(yàn)證失敗則錯(cuò)誤提示。
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 {
// 獲取請(qǐng)求頭,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;
}
//驗(yàn)證token
String sub = JWTUtils.validateToken(token);
if (sub == null || sub.equals("")){
log.info("token驗(yàn)證失敗");
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Token驗(yàn)證失敗!");
return false;
}
//更新token有效時(shí)間 (如果需要更新其實(shí)就是產(chǎn)生一個(gè)新的token)
if (JWTUtils.isNeedUpdate(token)){
String newToken = JWTUtils.createToken(sub);
response.setHeader(JWTUtils.header,newToken);
}
return true;
}
}
攔截器配置-注冊(cè)自定義攔截器
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 {
/**
* 注冊(cè)自定義攔截器
* @param registry ·
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new UserLoginInterceptor())
.addPathPatterns("/api/**") // 攔截地址
.excludePathPatterns("/api/userInfo/login");// 開(kāi)放登錄路徑
}
}
測(cè)試(Postman)
1.測(cè)試微信一鍵登錄?
微信小程序獲取臨時(shí)憑證code
?通過(guò)返回的code到postman中測(cè)試調(diào)用后端登錄接口
?獲取到返回,代表登錄成功。
2.測(cè)試token的驗(yàn)證?
調(diào)用非登錄接口,會(huì)被攔截進(jìn)行token的檢查。
后端日志輸出:?
?攜帶錯(cuò)誤或過(guò)期的token,驗(yàn)證失敗
?后端日志輸出?
?攜帶正確且在有效期內(nèi)的token,驗(yàn)證成功,測(cè)試通過(guò)。
總結(jié)
?對(duì)于如上代碼,其實(shí)微信登錄的邏輯是比較簡(jiǎn)單的,代碼更多的是在處理身份驗(yàn)證(token驗(yàn)證),后端設(shè)置了請(qǐng)求攔截器,會(huì)去攔截所有非登錄接口,通過(guò)檢查token判斷是否登錄過(guò)了。
對(duì)于前端發(fā)送請(qǐng)求,如上只是使用了Postman進(jìn)行接口的訪問(wèn),并沒(méi)有從代碼層面去發(fā)送請(qǐng)求,那么,其實(shí)前端是比較需要去封裝請(qǐng)求方法的,在封裝的請(qǐng)求方法中加入請(qǐng)求頭攜帶token,避免每一次請(qǐng)求都需要手動(dòng)加上請(qǐng)求頭攜帶token。文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-489226.html
如博文內(nèi)容存在不足之處請(qǐng)指出,感謝訪問(wèn)。文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-489226.html
到了這里,關(guān)于微信小程序一鍵登錄功能,使用uni-app和springboot(JWT鑒權(quán))的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!