微信小程序登录

一、登录流程:

 上面这个是微信官方给出的。在通过 wx.login() 获取到用户登录态之后,需要维护登录态。开发者要注意不应该直接把 session_key、openid 等字段作为用户的标识或者 session 的标识,而应该自己派发一个 session 登录状态。详见下面的流程图:

1.前端调用wx.login()获取code值(https://developers.weixin.qq.com/miniprogram/dev/api/open-api/login/wx.login.html);

2.前端通过调用wx.getUserInfo获取iv、rawData、signature、encryptedData等加密数据,传递给后端(https://developers.weixin.qq.com/miniprogram/dev/api/open-api/user-info/wx.getUserInfo.html);

3.服务器通过code请求api换回session_key和openid;

4.服务器通过前端给的rawData 加获取的session_key使用sha1加密,计算出signature1;

5.比对前端传的signature和自己算出来的signature1是否一致(防止数据不一致);

6.用AES算法解密encryptedData里的敏感数据;

7.拿着敏感数据后做自己的逻辑;

8.通知前端登陆成功;

二、首先调用wx.login获取 临时登录凭证 code

1 wx.login({
2   success: res => {
3     // 获取到用户的 code:res.code
4     const code = res.code;
5     console.log("用户的code:" + res.code);
6     //传入code调用服务端接口登录
7     return api.userLogin.registerOrLogin({ code });
8   }
9 })

Taro框架 是一个开放式跨端跨框架解决方案(关于taro相关文档可以参考:https://taro-docs.jd.com/docs/),支持使用 React/Vue/Nerv 等框架来开发应用。taro基本上做到了将微信小程序上的api都封装了,同时也紧随着小程序的更新。这个地方也可以用taro来调用:

1 const requestLogin = async () => {
2   const { code } = await Taro.login();
3   console.log("requestLogin code", code);
4   return api.userLogin.registerOrLogin({ code });
5 };

三、服务端后台根据code获取openId、session_key和unionId:

1 @PostMapping("/user-login/action/registerOrLogin")
2 public Response<LoginVO> registerOrLogin(@RequestBody UserLoginDTO userLoginDTO) {
3   return ResponseUtils.returnObjectSuccess(userLoginService.registerOrLoginByInfo(userLoginDTO));
4 }
 1 public LoginVO registerOrLoginByInfo(UserLoginDTO userLoginDTO) {
 2     UserAccount userAccount = new UserAccount();
 3     result = authorizationGrantApi.wxMiniGrant(dto.getCode());
 4     if (result != null) {
 5         if (StringUtils.hasText(result.getOpenId())) {
 6             userAccount.setOpenId(result.getOpenId());
 7         }
 8         if (StringUtils.hasText(result.getUnionId())) {
 9             userAccount.setUnionId(result.getUnionId());
10         }
11     }
12     
13     if (StringUtils.hasText(dto.getEncryptedData()) && StringUtils.hasText(dto.getIv())) {
14     ObjectNode userNode = weChatMiniGrantApi.decryptUser(dto.getEncryptedData(), result.getAuthorId(), dto.getIv());
15     //基本信息
16     if (userNode.get("openId") != null) {
17         userAccount.setOpenId(userNode.get("openId").textValue());
18         userAccount.setGender(userNode.get("gender").intValue());
19         userAccount.setNickName(userNode.get("nickName").textValue());
20         userAccount.setAvatar(userNode.get("avatarUrl").textValue());
21         if (userNode.get("unionId") != null) {
22             userAccount.setUnionId(userNode.get("unionId").textValue());
23         }
24     } else {
25         userAccount.setUserPhone(userNode.get("phoneNumber").textValue());
26     }
27     
28     LoginVO vo = new LoginVO();
29     vo.setUserAccount(userAccount);
30     ..........
31     return vo;
32 }

其中的authorizationGrantApi.wxMiniGrant(dto.getCode())实现了根据code获取openId和unionId:

 1 /**
 2  * 微信小程序授权
 3  *
 4  * @param header
 5  * @param authCode
 6  * @return
 7  */
 8 public AuthorizerGrantResult wxMiniGrant(YryzRequestHeader header, String authCode) {
 9     return this.wxMiniGrantAndDecryUser(header, authCode, null, null);
10 }
11 
12 public AuthorizerGrantResult wxMiniGrantAndDecryUser(String authCode, String encryptedData, String iv) {
13     if (weChatMiniGrantApi == null) {
14         throw new BusinessException("", "暂未开放微信小程序授权");
15     }
16 
17     //根据code,调用code2Session得到openid, session_key和unionId
18     JsonNode respNode = weChatMiniGrantApi.miniGrant(authCode);
19 
20     /**
21      * 转换基本参数
22      */
23     AuthorizerGrantResult base = new AuthorizerGrantResult();
24     base.setOpenId(respNode.get("openid").textValue());
25     if(respNode.get("unionid") != null){
26         base.setUnionId(respNode.get("unionid").textValue());
27     }
28     base.setChannelId(IAuthorizerGrantHook.WX_MINI);
29 
30     /**
31      * 解密用户信息数据
32      */
33     if (StringUtils.hasText(encryptedData) && StringUtils.hasText(iv)) {
34         ObjectNode userNode = weChatMiniGrantApi.decryptUser(encryptedData, respNode.get("session_key").textValue(), iv);
35         //基本信息
36         AuthorizerGrantResult.ChannelAuthor channelAuthor = new AuthorizerGrantResult.ChannelAuthor(userNode);
37         channelAuthor.setAvatar(userNode.get("avatarUrl").textValue());
38         channelAuthor.setGender(userNode.get("gender").intValue());
39         channelAuthor.setNickName(userNode.get("nickName").textValue());
40         channelAuthor.setPhone(userNode.get("phoneNumber").textValue());
41         base.setChannelAuthor(channelAuthor);
42     }
43     return authorizerGrantHook.successAfter(header, IAuthorizerGrantHook.WX_MINI, base, respNode);
44 }

上面代码中的wxMiniGrantAndDecryUser首先根据传入的code调用调用code2Session得到openid, session_key和unionId,然后从encryptedData和iv数据解密得到用户数据信息。解密用户数据信息是调用的decryptUser函数,其实现代码如下:

四、解密encryptedData数据得到用户信息:

 1 public ObjectNode decryptUser(String encryptedData, String sessionKey, String iv) {
 2     // 被加密的数据
 3     byte[] dataByte = Base64Utils.decodeFromString(encryptedData);
 4     // 加密秘钥
 5     byte[] keyByte = Base64Utils.decodeFromString(sessionKey);
 6     // 偏移量
 7     byte[] ivByte = Base64Utils.decodeFromString(iv);
 8     try {
 9         // 如果密钥不足16位,那么就补足. 这个if 中的内容很重要
10         int base = 16;
11         if (keyByte.length % base != 0) {
12             int groups = keyByte.length / base + 1;
13             byte[] temp = new byte[groups * base];
14             Arrays.fill(temp, (byte) 0);
15             System.arraycopy(keyByte, 0, temp, 0, keyByte.length);
16             keyByte = temp;
17         }
18 
19         // 初始化
20         Security.addProvider(new BouncyCastleProvider());
21         Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding", "BC");
22         SecretKeySpec spec = new SecretKeySpec(keyByte, "AES");
23         AlgorithmParameters parameters = AlgorithmParameters.getInstance("AES");
24         parameters.init(new IvParameterSpec(ivByte));
25         cipher.init(Cipher.DECRYPT_MODE, spec, parameters);// 初始化
26         byte[] resultByte = cipher.doFinal(dataByte);
27 
28         return objectMapper.readValue(new String(resultByte, StandardCharsets.UTF_8), ObjectNode.class);
29     } catch (Exception e) {
30         logger.error("用户信息解密失败:{}", e.getMessage());
31     }
32 }

 

posted on 2023-12-13 10:31  小夏coding  阅读(133)  评论(0编辑  收藏  举报

导航