( 十 )、SpringBoot整合微信小程序登錄


( 十 )、SpringBoot整合微信小程序登錄

 

 

 

1. 微信小程序登錄流程

微信小程序登錄流程涉及到三個角色:小程序、開發者服務器、微信服務器

三者交互步驟如下:

第一步:小程序通過wx.login()獲取code。
第二步:小程序通過wx.request()發送code到開發者服務器。
第三步:開發者服務器接收小程序發送的code,並攜帶appid、appsecret(這兩個需要到微信小程序后台查看)、code發送到微信服務器
第四步:微信服務器接收開發者服務器發送的appid、appsecret、code進行校驗。校驗通過后向開發者服務器發送session_key、openid。
第五步:開發者服務器自己生成一個skey(自定義登錄狀態)與openid、session_key進行關聯,並存到數據庫中(mysql、redis等)。
第六步:開發者服務器返回生成skey(自定義登錄狀態)到小程序。
第七步:小程序存儲skey(自定義登錄狀態)到本地。
第八步:小程序通過wx.request()發起業務請求到開發者服務器,同時攜帶skey(自定義登錄狀態)。
第九步:開發者服務器接收小程序發送的skey(自定義登錄狀態),查詢skey在數據庫中是否有對應的openid、session_key。
第十步:開發者服務器返回業務數據到小程序

yml:

<!--hutool具包-->  
<dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>5.4.0</version> </dependency>
<!--簡化代碼的工具包--> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency>
<!--  mybatis-plus-spring-boot-starter-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.3.2</version>
</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;
    /**
     * 加密的數據
     */
    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<String, Object> requestUrlParam = new HashMap<>();
        //小程序appId
        requestUrlParam.put("appid", "小程序appId");
        //小程序secret
        requestUrlParam.put("secret", "小程序secret");
        //小程序端返回的code
        requestUrlParam.put("js_code", code);
        //默認參數
        requestUrlParam.put("grant_type", "authorization_code");
        //發送post請求讀取調用微信接口獲取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 {
        // 被加密的數據
        byte[] dataByte = Base64.decode(encryptedData);
        // 加密秘鑰
        byte[] keyByte = Base64.decode(sessionKey);
        // 偏移量
        byte[] ivByte = Base64.decode(iv);
        try {
            // 如果密鑰不足16位,那么就補足.  這個if 中的內容很重要
            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.開發者服務器 登錄憑證校驗接口 appId + appSecret + 接收小程序發送的code
        JSONObject SessionKeyOpenId = WeChatUtil.getSessionKeyOrOpenId(weChatUserInfo.getCode());
        // 3.接收微信接口服務 獲取返回的參數
        String openid = SessionKeyOpenId.get("openid", String.class);
        String sessionKey = SessionKeyOpenId.get("session_key", String.class);
        // 用戶非敏感信息:rawData
        // 簽名:signature
        JSONObject rawDataJson = JSONUtil.parseObj(weChatUserInfo.getRawData());
        // 4.校驗簽名 小程序發送的簽名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.根據返回的User實體類,判斷用戶是否是新用戶,是的話,將用戶信息存到數據庫;不是的話,更新最新登錄時間
        QueryWrapper<User> 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);
           // 新增用戶到數據庫
        } else {
            // 已存在,更新用戶登錄時間

        }
        //6. 把新的skey返回給小程序
        return ResultInfo.success();
    }


}

 

全局返回結果:

public class ResultInfo {
    /**
     * 響應代碼
     */
    private String code;

    /**
     * 響應消息
     */
    private String message;

    /**
     * 響應結果
     */
    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;
    }
}

 

4. 微信小程序

4.1 初始配置

 
初始配置

4.2 me.wxml

<view class="container">
  <!-- 登錄組件 https://developers.weixin.qq.com/miniprogram/dev/api/wx.getUserInfo.html --> 
  <button wx:if="{{!hasUserInfo}}" open-type="getUserInfo" bind:getuserinfo="onGetUserInfo">授權登錄</button>
  <!-- 登錄后使用open-data -->
  <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>

4.3 me.wxss

4.4 me.json

{
  
}

4.5 me.js

// pages/me/me.js
Page({

  /**
   * 頁面的初始數據
   */
  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()發送code到開發者服務器
              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(自定義登錄狀態)到本地
                    wx.setStorageSync('userInfo', userInfo);
                    wx.setStorageSync('skey', res.data.data);
                  } else{
                    console.log('服務器異常');
                  }
                },
                fail: function(error) {
                  //調用服務端登錄接口失敗
                  console.log(error);
                }
              })
            }
          })
        }
      })
      this.setData({
        hasUserInfo: true,
        userInfo: userInfo
      })
    }
  }

})

 

4.6 app.json

設置app.json的pages

{
  "pages":[
    "pages/me/me"
  ],
  "window":{
    "backgroundTextStyle":"light",
    "navigationBarBackgroundColor": "#fff",
    "navigationBarTitleText": "WeChat",
    "navigationBarTextStyle":"black"
  },
  "debug":true
}

 

5. 測試

啟動開發者服務器,啟動SpringBoot的main方法。

打開微信小程序開發者工具

 
清空緩存

點擊授權登錄,並允許。

 
授權登錄

登錄成功

查看數據庫,openid、skey以及用戶信息等存入了數據庫。

用戶信息入庫

同時微信小程序將skey等存儲到本地,每次發起請求時都可以攜帶上。

 
 
 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM