微信小程序登录
一、登录流程:
上面这个是微信官方给出的。在通过 wx.login() 获取到用户登录态之后,需要维护登录态。开发者要注意不应该直接把 session_key、openid 等字段作为用户的标识或者 session 的标识,而应该自己派发一个 session 登录状态。详见下面的流程图:
二、
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 })
1 const requestLogin = async () => { 2 const { code } = await Taro.login(); 3 console.log("requestLogin code", code); 4 return api.userLogin.registerOrLogin({ code }); 5 };
三、
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函数,其实现代码如下:
四、
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 }