SpringBoot整合微信小程序登錄
點擊上方藍色字體,選擇“標星公眾號”
優(yōu)質(zhì)文章,第一時間送達
? 作者?|??鄧維-java
來源 |? urlify.cn/aEB3Qj ??
微信小程序登錄流程
微信小程序登錄流程涉及到三個角色:小程序、開發(fā)者服務器、微信服務器
三者交互步驟如下:
第一步:小程序通過wx.login()獲取code。
第二步:小程序通過wx.request()發(fā)送code到開發(fā)者服務器。
第三步:開發(fā)者服務器接收小程序發(fā)送的code,并攜帶appid、appsecret(這兩個需要到微信小程序后臺查看)、code發(fā)送到微信服務器。
第四步:微信服務器接收開發(fā)者服務器發(fā)送的appid、appsecret、code進行校驗。校驗通過后向開發(fā)者服務器發(fā)送session_key、openid。
第五步:開發(fā)者服務器自己生成一個skey(自定義登錄狀態(tài))與openid、session_key進行關聯(lián),并存到數(shù)據(jù)庫中(mysql、redis等)。
第六步:開發(fā)者服務器返回生成skey(自定義登錄狀態(tài))到小程序。
第七步:小程序存儲skey(自定義登錄狀態(tài))到本地。
第八步:小程序通過wx.request()發(fā)起業(yè)務請求到開發(fā)者服務器,同時攜帶skey(自定義登錄狀態(tài))。
第九步:開發(fā)者服務器接收小程序發(fā)送的skey(自定義登錄狀態(tài)),查詢skey在數(shù)據(jù)庫中是否有對應的openid、session_key。
第十步:開發(fā)者服務器返回業(yè)務數(shù)據(jù)到小程序。

yml:
???<dependency>
????????????<groupId>cn.hutoolgroupId>
????????????<artifactId>hutool-allartifactId>
????????????<version>5.4.0version>
?????dependency>
????????<dependency>
????????????<groupId>org.projectlombokgroupId>
????????????<artifactId>lombokartifactId>
????????????<optional>trueoptional>
????????dependency>
<dependency>????<groupId>com.baomidougroupId>????<artifactId>mybatis-plus-boot-starterartifactId>????<version>3.3.2version>dependency>wx返回的用戶信息:
/**
?* @Author?dw
?* @ClassName?WeChatUserInfo
?* @Description?微信用戶信息
?* @Date?2020/8/28 14:14
?* @Version?1.0
?*/
@Data
public?class?WeChatUserInfo?{
????/**
?????* 微信返回的code
?????*/
????private?String code;
????/**
?????* 非敏感的用戶信息
?????*/
????private?String rawData;
????/**
?????* 簽名信息
?????*/
????private?String signature;
????/**
?????* 加密的數(shù)據(jù)
?????*/
????private?String encrypteData;
????/**
?????* 加密密鑰
?????*/
????private?String iv;
}WeChatUtil工具:/**
?* @Author dw
?* @ClassName WeChatUtil
?* @Description
?* @Date 2020/8/28 10:56
?* @Version 1.0
?*/
public?class?WeChatUtil?{
????public?static?JSONObject getSessionKeyOrOpenId(String code) {
????????String requestUrl = "https://api.weixin.qq.com/sns/jscode2session";
????????HashMap requestUrlParam = new?HashMap<>();
????????//小程序appId
????????requestUrlParam.put("appid", "小程序appId");
????????//小程序secret
????????requestUrlParam.put("secret", "小程序secret");
????????//小程序端返回的code
????????requestUrlParam.put("js_code", code);
????????//默認參數(shù)
????????requestUrlParam.put("grant_type", "authorization_code");
????????//發(fā)送post請求讀取調(diào)用微信接口獲取openid用戶唯一標識
????????String result = HttpUtil.get(requestUrl, requestUrlParam);
????????JSONObject jsonObject = JSONUtil.parseObj(result);
????????return?jsonObject;
????}
????public?static?JSONObject getUserInfo(String encryptedData, String sessionKey, String iv) throws Base64DecodingException {
????????// 被加密的數(shù)據(jù)
????????byte[] dataByte = Base64.decode(encryptedData);
????????// 加密秘鑰
????????byte[] keyByte = Base64.decode(sessionKey);
????????// 偏移量
????????byte[] ivByte = Base64.decode(iv);
????????try?{
????????????// 如果密鑰不足16位,那么就補足. 這個if 中的內(nèi)容很重要
????????????int?base?= 16;
????????????if?(keyByte.length % base?!= 0) {
????????????????int?groups = keyByte.length / base?+ (keyByte.length % base?!= 0?? 1?: 0);
????????????????byte[] temp = new?byte[groups * base];
????????????????Arrays.fill(temp, (byte) 0);
????????????????System.arraycopy(keyByte, 0, temp, 0, keyByte.length);
????????????????keyByte = temp;
????????????}
????????????// 初始化
????????????Security.addProvider(new?BouncyCastleProvider());
????????????Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding", "BC");
????????????SecretKeySpec spec = new?SecretKeySpec(keyByte, "AES");
????????????AlgorithmParameters parameters = AlgorithmParameters.getInstance("AES");
????????????parameters.init(new?IvParameterSpec(ivByte));
????????????// 初始化
????????????cipher.init(Cipher.DECRYPT_MODE, spec, parameters);
????????????byte[] resultByte = cipher.doFinal(dataByte);
????????????if?(null?!= resultByte && resultByte.length > 0) {
????????????????String result = new?String(resultByte, "UTF-8");
????????????????return?JSONUtil.parseObj(result);
????????????}
????????} catch?(Exception e) {
????????}
????????return?null;
????} 登錄controller:
/**
?* @Author dw
?* @ClassName WeChatUserLoginController
?* @Description
?* @Date 2020/8/28 14:12
?* @Version 1.0
?*/
@RestController
public?class?WeChatUserLoginController {
????@Resource
????private?IUserService userService;
????/**
?????* 微信用戶登錄詳情
?????*/
????@PostMapping("wx/login")
????public?ResultInfo user_login(@RequestBody?WeChatUserInfo weChatUserInfo) throws Base64DecodingException {
????????// 2.開發(fā)者服務器 登錄憑證校驗接口 appId + appSecret + 接收小程序發(fā)送的code
????????JSONObject SessionKeyOpenId = WeChatUtil.getSessionKeyOrOpenId(weChatUserInfo.getCode());
????????// 3.接收微信接口服務 獲取返回的參數(shù)
????????String?openid = SessionKeyOpenId.get("openid", String.class);
????????String?sessionKey = SessionKeyOpenId.get("session_key", String.class);
????????// 用戶非敏感信息:rawData
????????// 簽名:signature
????????JSONObject rawDataJson = JSONUtil.parseObj(weChatUserInfo.getRawData());
????????// 4.校驗簽名 小程序發(fā)送的簽名signature與服務器端生成的簽名signature2 = sha1(rawData + sessionKey)
????????String?signature2 = DigestUtils.sha1Hex(weChatUserInfo.getRawData() + sessionKey);
????????if?(!weChatUserInfo.getSignature().equals(signature2)) {
????????????return?ResultInfo.error( "簽名校驗失敗");
????????}
????????//encrypteData比rowData多了appid和openid
????????JSONObject userInfo = WeChatUtil.getUserInfo(weChatUserInfo.getEncrypteData(),
????????????????sessionKey, weChatUserInfo.getIv());
????????// 5.根據(jù)返回的User實體類,判斷用戶是否是新用戶,是的話,將用戶信息存到數(shù)據(jù)庫;不是的話,更新最新登錄時間
????????QueryWrapper userQueryWrapper = new?QueryWrapper<>();
????????userQueryWrapper.lambda().eq(User::getLoginName, openid);
????????int userCount = userService.count(userQueryWrapper);
????????// uuid生成唯一key,用于維護微信小程序用戶與服務端的會話(或者生成Token)
????????String?skey = UUID.randomUUID().toString();
????????if?(userCount <= 0) {
????????????// 用戶信息入庫
????????????String?nickName = rawDataJson.get("nickName",String.class);
????????????String?avatarUrl = rawDataJson.get("avatarUrl",String.class);
????????????String?gender = rawDataJson.get("gender",String.class);
????????????String?city = rawDataJson.get("city",String.class);
????????????String?country = rawDataJson.get("country",String.class);
????????????String?province = rawDataJson.get("province",String.class);
???????????// 新增用戶到數(shù)據(jù)庫
????????} else?{
????????????// 已存在,更新用戶登錄時間
????????}
????????//6. 把新的skey返回給小程序
????????return?ResultInfo.success();
????}
} 全局返回結(jié)果:
public?class?ResultInfo?{
????/**
?????* 響應代碼
?????*/
????private?String code;
????/**
?????* 響應消息
?????*/
????private?String message;
????/**
?????* 響應結(jié)果
?????*/
????private?Object result;
????public?ResultInfo() {
????}
????public?ResultInfo(BaseErrorInfoInterface errorInfo) {
????????this.code = errorInfo.getResultCode();
????????this.message = errorInfo.getResultMsg();
????}
????public?String getCode() {
????????return?code;
????}
????public?void?setCode(String code) {
????????this.code = code;
????}
????public?String getMessage() {
????????return?message;
????}
????public?void?setMessage(String message) {
????????this.message = message;
????}
????public?Object getResult() {
????????return?result;
????}
????public?void?setResult(Object result) {
????????this.result = result;
????}
????/**
?????* 成功
?????*
?????* @return
?????*/
????public?static?ResultInfo success() {
????????return?success(null);
????}
????/**
?????* 成功
?????* @param data
?????* @return
?????*/
????public?static?ResultInfo success(Object data) {
????????ResultInfo rb = new?ResultInfo();
????????rb.setCode(CommonEnum.SUCCESS.getResultCode());
????????rb.setMessage(CommonEnum.SUCCESS.getResultMsg());
????????rb.setResult(data);
????????return?rb;
????}
????/**
?????* 失敗
?????*/
????public?static?ResultInfo error(BaseErrorInfoInterface errorInfo) {
????????ResultInfo rb = new?ResultInfo();
????????rb.setCode(errorInfo.getResultCode());
????????rb.setMessage(errorInfo.getResultMsg());
????????rb.setResult(null);
????????return?rb;
????}
????/**
?????* 失敗
?????*/
????public?static?ResultInfo error(String code, String message) {
????????ResultInfo rb = new?ResultInfo();
????????rb.setCode(code);
????????rb.setMessage(message);
????????rb.setResult(null);
????????return?rb;
????}
????/**
?????* 失敗
?????*/
????public?static?ResultInfo error(String message) {
????????ResultInfo rb = new?ResultInfo();
????????rb.setCode("-1");
????????rb.setMessage(message);
????????rb.setResult(null);
????????return?rb;
????}}?微信小程序
項目結(jié)構(gòu):

項目結(jié)構(gòu)
1 初始配置
?

初始配置
2 me.wxml
<view?class="container">
????
??<button?wx:if="{{!hasUserInfo}}"?open-type="getUserInfo"?bind:getuserinfo="onGetUserInfo">授權登錄button>
??
??<view?class="avatar-container avatar-position">
??????<image?src="{{userInfo.avatarUrl}}"?wx:if="{{hasUserInfo}}"?class="avatar"?/>
??????<open-data?wx:if="{{hasUserInfo}}"?type="userNickName">open-data>
??view>
view>3 me.wxss
無
4 me.json
{
??
}5 me.js
// pages/me/me.js
Page({
??/**
???* 頁面的初始數(shù)據(jù)
???*/
??data: {
????hasUserInfo: false,
????userInfo: null
??},
??onLoad: function() {
????// 頁面加載時使用用戶授權邏輯,彈出確認的框
????this.userAuthorized()
??},
??
??userAuthorized() {
????wx.getSetting({
??????success: data?=>?{
????????if?(data.authSetting['scope.userInfo']) {
??????????wx.getUserInfo({
????????????success: data?=>?{
??????????????this.setData({
????????????????hasUserInfo: true,
????????????????userInfo: data.userInfo
??????????????})
????????????}
??????????})
????????} else?{
??????????this.setData({
????????????hasUserInfo: false
??????????})
????????}
??????}
????})
??},
??onGetUserInfo(e) {
????const?userInfo = e.detail.userInfo
????if?(userInfo) {
??????// 1. 小程序通過wx.login()獲取code
??????wx.login({
????????success: function(login_res) {
??????????//獲取用戶信息
??????????wx.getUserInfo({
????????????success: function(info_res) {
??????????????// 2. 小程序通過wx.request()發(fā)送code到開發(fā)者服務器
??????????????wx.request({
????????????????url: 'http://localhost:8080/wx/login',
????????????????method: 'POST',
????????????????header: {
?????????????????'content-type': 'application/json'
????????????????},
????????????????data: {
??????????????????code: login_res.code, //臨時登錄憑證
??????????????????rawData: info_res.rawData, //用戶非敏感信息
??????????????????signature: info_res.signature, //簽名
??????????????????encrypteData: info_res.encryptedData, //用戶敏感信息
??????????????????iv: info_res.iv //解密算法的向量
????????????????},
????????????????success: function(res) {
??????????????????if?(res.data.status == 200) {
????????????????????// 7.小程序存儲skey(自定義登錄狀態(tài))到本地
????????????????????wx.setStorageSync('userInfo', userInfo);
????????????????????wx.setStorageSync('skey', res.data.data);
??????????????????} else{
????????????????????console.log('服務器異常');
??????????????????}
????????????????},
????????????????fail: function(error) {
??????????????????//調(diào)用服務端登錄接口失敗
??????????????????console.log(error);
????????????????}
??????????????})
????????????}
??????????})
????????}
??????})
??????this.setData({
????????hasUserInfo: true,
????????userInfo: userInfo
??????})
????}
??}
})6 app.json
設置app.json的pages
{
??"pages":[
????"pages/me/me"
??],
??"window":{
????"backgroundTextStyle":"light",
????"navigationBarBackgroundColor": "#fff",
????"navigationBarTitleText": "WeChat",
????"navigationBarTextStyle":"black"
??},
??"debug":true
}測試
啟動開發(fā)者服務器,啟動SpringBoot的main方法。
打開微信小程序開發(fā)者工具
?

清空緩存
點擊授權登錄,并允許。

授權登錄
登錄成功
?

登錄成功
查看數(shù)據(jù)庫,openid、skey以及用戶信息等存入了數(shù)據(jù)庫。
?

用戶信息入庫
同時微信小程序?qū)key等存儲到本地,每次發(fā)起請求時都可以攜帶上。
?

skey存儲本地
粉絲福利:108本java從入門到大神精選電子書領取
???
?長按上方鋒哥微信二維碼?2 秒 備注「1234」即可獲取資料以及 可以進入java1234官方微信群
感謝點贊支持下哈?
