WeChat applet Java login process (ssm realizes specific functions and solves the problem of encryption and decryption of private information)

WeChat applet Java login process (ssm realizes specific functions and solves the problem of encryption and decryption of private information)

If there is something wrong with the article, please correct me. If you like to read on WeChat, you can also follow my WeChat official account:, javato get high-quality learning resources.

1. Login flow chart

2. Mini Program Client

doLogin:function(callback = () =>{}){
let that = this;
wx.login({
  success:function(loginRes){
    if(loginRes){
     //
      wx.getUserInfo({
        withCredentials:true,//   true
        success:function(infoRes){
          console.log(infoRes,'>>>');
         //
          wx.request({
            url: api.loginUrl,
            data:{
              code:loginRes.code,//
              rawData:infoRes.rawData,//
              signature:infoRes.signature,//
              encrypteData:infoRes.encryptedData,//
              iv:infoRes.iv//
            },
            success:function(res){
              console.log('login success');
              res = res.data;
              if(res.result==0){
                that.globalData.userInfo = res.userInfo;
                wx.setStorageSync('userInfo',JSON.stringify(res.userInfo));
                wx.setStorageSync('loginFlag',res.skey);
                console.log("skey="+res.skey);
                callback();
              }else{
                that.showInfo('res.errmsg');
              }
            },
            fail:function(error){
             //
            //that.showInfo(' ');
              console.log(error);
            }
          });
        }
      });
    }else{
 
    }
  }
});
}
 

The WeChat applet initiates a login request, and the main parameters carried are:

    code:loginRes.code,//
    rawData:infoRes.rawData,//
    signature:infoRes.signature,//
    encrypteData:infoRes.encryptedData,//
    iv:infoRes.iv//
 

Parameter explanation: code:loginRes.code,//Temporary login credentials: must be passed , exchange the background sessionKeyand openId rawData:infoRes.rawData through code ,//User non-sensitive information signature:infoRes.signature,//Signature encrypteData:infoRes. encryptedData,//User sensitive information iv:infoRes.iv//Vector of decryption algorithm

signature ,//signature, encryptedData ,//user sensitive information, iv//vector of decryption algorithm:

These three parameters are used to decode sensitive user information, such as phone numbers and other information.

The main data needed are:, skeyused to mark the uniqueness of the user.

3. Java background

/**
     *  
     */
    @RequestMapping("/login")
    @ApiResponses({
            @ApiResponse(code = 404, message = " "),
            @ApiResponse(code = 200, message = " "),
            @ApiResponse(code = 500, message = " "),
            @ApiResponse(code = 401, message = " "),
            @ApiResponse(code = 403, message = " "),
    })
    @ApiOperation(value = " ", httpMethod = "POST", notes = " ")
    public ResponseEntity<LoginDataResult> login(
            @ApiParam(required = true, value = " code", name = "code") String code,
            @ApiParam(required = true, value = " ", name = "rawData")
            @RequestParam(value = "rawData", required = true) String rawData,
            @ApiParam(required = true, value = " ", name = "signature")
            @RequestParam(value = "signature", required = true) String signature,
            @ApiParam(required = true, value = " ", name = "encrypteData")
            @RequestParam(value = "encrypteData", required = true) String encrypteData,
            @ApiParam(required = true, value = " ", name = "iv")
            @RequestParam(value = "iv", required = true) String iv
    ) {

        ObjectMapper mapper = new ObjectMapper();

        logger.info("signature============================================================="+signature);
        logger.info("encrypteData=========================================================="+encrypteData);
        logger.info("iv========================================================================"+iv);

        RawData data = null;
        WxMaJscode2SessionResult session = null;
        String openid = null;
        String sessionKey = null;
        String phoneNumber = null;

        try {
            if (rawData != null && !"".equals(rawData)) {
               //1 
                data = mapper.readValue(rawData, RawData.class);
            }
            session = this.wxService.getUserService().getSessionInfo(code);

           //openid sessionkey
            openid = session.getOpenid();
            sessionKey = session.getSessionKey();

            logger.info("sessionkey========================================================="+sessionKey);

         /* //2 
            phoneNumber = phone(code, signature, rawData, encrypteData, iv);

            logger.info("phoneNumber========================================="+phoneNumber);
*/
        } catch (IOException e) {
            e.printStackTrace();
            logger.info(" ");
            LoginDataResult loginDataResult = new LoginDataResult();
            loginDataResult.setCode("2");
            loginDataResult.setMsg(" ");
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(loginDataResult);
        } catch (WxErrorException e) {
            e.printStackTrace();
            logger.info(" ");
        }

       //3 
        String skey = insertUser(data, openid, phoneNumber);

       //4 openid, sessionKey, skey redis
        redisCache(openid, sessionKey, skey);


       //5 skey 
        LoginDataResult loginDataResult = new LoginDataResult();
        loginDataResult.setSkey(skey);
        loginDataResult.setCode("1");
        loginDataResult.setMsg(" ");

        return ResponseEntity.status(HttpStatus.OK).body(loginDataResult);
    }

   /**
     *  openid sessionKey,skey 
     * @param openid  
     * @param sessionKey  
     * @param skey  
     */
    private void redisCache(String openid, String sessionKey, String skey) {
       //openid skey 
        String skey_redis = jedisClient.hget("WEXIN_USER_OPENID_SKEY", openid);
        if (StringUtils.isNotBlank(skey_redis)) {
           //   skey  skey  skey 
            jedisClient.hdel("WEXIN_USER_OPENID_SKEY", openid);
            jedisClient.hdel("WEIXIN_USER_SKEY_OPENID", skey_redis);
            jedisClient.hdel("WEIXIN_USER_SKEY_SESSIONKEY", skey_redis);
        }

       //  
        jedisClient.hset("WEXIN_USER_OPENID_SKEY", openid, skey);
        jedisClient.expire("WEXIN_USER_OPENID_SKEY",432000);//5 
        jedisClient.hset("WEIXIN_USER_SKEY_OPENID", skey, openid);
        jedisClient.expire("WEIXIN_USER_SKEY_OPENID",432000);//5 
        jedisClient.hset("WEIXIN_USER_SKEY_SESSIONKEY", skey, sessionKey);
        jedisClient.expire("WEIXIN_USER_SKEY_SESSIONKEY",432000);//5 
    }

   /**
     *  
     * @param data  
     * @param openid
     * @param phoneNumber  
     * @return
     */
    private String insertUser(RawData data, String openid, String phoneNumber) {
       //, 
        Member user = userService.selectUserByOpenid(openid);
       //uuid key
        String skey = UUID.randomUUID().toString();
        if (user == null) {
           //
            user = new Member();
            user.setId(skey);
            user.setCountry(data.getCountry());
            user.setCreatedate(new Date());
            user.setDf(1);
            user.setGender(data.getGender().equals("1") ? 1 : 2);//1 2 
            user.setHeadimg(data.getAvatarUrl());
            user.setNickname(data.getNickName());
            user.setOpenid(openid);
            user.setCitycode(data.getCity());
            user.setProvincecode(data.getProvince());
            user.setMobileno(phoneNumber);
           //
            userService.insertUser(user);
        } else {
           //
            logger.info(" openid , ");
            return user.getId();//skey
        }
        return skey;
    }

   /**
     *  
     * @param sessionKey  session
     * @param signature  
     * @param rawData  
     * @param encryptedData  
     * @param iv  
     * @return
     */
    @ApiOperation(value = " ", httpMethod = "GET", notes = " ")
    public String phone(String sessionKey, String signature, String rawData, String encryptedData, String iv) {
        String phoneNumber = null;

        try {
            byte[] bytes = WxMiniappUtils.decrypt(Base64.decodeBase64(sessionKey), Base64.decodeBase64(iv), Base64.decodeBase64(encryptedData));
            String phone = new String(bytes, "UTF8");
            logger.info("phone====================================="+phone);
        } catch (NoSuchPaddingException e) {
            e.printStackTrace();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (InvalidAlgorithmParameterException e) {
            e.printStackTrace();
        } catch (InvalidKeyException e) {
            e.printStackTrace();
        } catch (BadPaddingException e) {
            e.printStackTrace();
        } catch (IllegalBlockSizeException e) {
            e.printStackTrace();
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return null;
    }
 

The following analysis of the above code:

3.1 Get openid and sessionKey

session = this.wxService.getUserService().getSessionInfo(code);

//openid sessionkey
openid = session.getOpenid();
sessionKey = session.getSessionKey();
 

Is this code very concise? A third-party SDK (weixin-java-tools) is used here . Through this SDK, you can easily get the openid and sessionKey, the specific demo .

Of course, if you don t want to use it sdk, you can also implement it yourself. The implementation code is as follows:

public static JSONObject getSessionKeyOrOpenId(String code){
   //code
    String wxCode = code;
    String requestUrl = "https://api.weixin.qq.com/sns/jscode2session";
    Map<String,String> requestUrlParam = new HashMap<String, String>(  );
    requestUrlParam.put( "appid"," appId" );//appId
    requestUrlParam.put( "secret"," appSecret" );
    requestUrlParam.put( "js_code",wxCode );//code
    requestUrlParam.put( "grant_type","authorization_code" );//
 
   //post openid 
    JSONObject jsonObject = JSON.parseObject( UrlUtil.sendPost( requestUrl,requestUrlParam ));
    return jsonObject;
}
 

3.2 Decrypt user sensitive data to obtain user information

3.2.1controller

This part has encountered a lot of pits, because it needs to obtain the user's mobile phone number, it needs to decrypt the user's information.

 /**
     *  
     * @param sessionKey  session
     * @param signature  
     * @param rawData  
     * @param encryptedData  
     * @param iv  
     * @return
     */
    @ApiOperation(value = " ", httpMethod = "GET", notes = " ")
    public String phone(String sessionKey, String signature, String rawData, String encryptedData, String iv) {
        String phoneNumber = null;

        try {
            byte[] bytes = WxMiniappUtils.decrypt(Base64.decodeBase64(sessionKey), Base64.decodeBase64(iv), Base64.decodeBase64(encryptedData));
            String phone = new String(bytes, "UTF8");
            logger.info("phone====================================="+phone);
        } catch (NoSuchPaddingException e) {
            e.printStackTrace();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (InvalidAlgorithmParameterException e) {
            e.printStackTrace();
        } catch (InvalidKeyException e) {
            e.printStackTrace();
        } catch (BadPaddingException e) {
            e.printStackTrace();
        } catch (IllegalBlockSizeException e) {
            e.printStackTrace();
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return null;
    }
 
3.2.2decrypt tool class

WxMiniappUtils.decryptThis tool class is called here , the tool class is as follows:

/**
     *  
     * @param sessionkey  sessionKey
     * @param iv  
     * @param encryptedData
     * @return
     * @throws NoSuchPaddingException
     * @throws NoSuchAlgorithmException
     * @throws InvalidAlgorithmParameterException
     * @throws InvalidKeyException
     * @throws BadPaddingException
     * @throws IllegalBlockSizeException
     */
    public static byte[] decrypt(byte[] sessionkey, byte[] iv, byte[] encryptedData)
            throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException,
            InvalidKeyException, BadPaddingException, IllegalBlockSizeException {
        AlgorithmParameterSpec ivSpec = new IvParameterSpec(iv);
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        SecretKeySpec keySpec = new SecretKeySpec(sessionkey, "AES");
        cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
        return cipher.doFinal(encryptedData);
    }
 

Here used Cipherclass is javax.cryptoclass.

3.2.3 Problem

But decryptwhen using this tool class, I encountered a lot of problems.

First : AES decryption is an error javax.crypto.BadPaddingException: pad block corrupted

This problem is due to the use of tools Cipher.getInstance("AES/CBC/PKCS5Padding").

Solution : Cipher cipher = Cipher.getInstance("AES/ECB/ZeroBytePadding");.

Second : java.security.InvalidAlgorithmParameterException: Wrong IV length: must be 16 This problem is because the decoded iv is not 16 bits, it seems to be 15 bits. I don't know why.

Solution : How to solve this, I have not found a solution, if there is a great god to solve it, please let me know!

My solution : In fact, I found that this problem is not the problem of this tool class. After a day of tossing, I found that this tool class is not unable to decode mobile phone numbers. Some are possible, some cannot resolve mobile phone numbers, only ordinary ones. Information, so I think this may be when a WeChat user registered, was it registered with a mobile phone number, so there will be some that can be parsed, and some can t be parsed. If there are other ways for great gods, please let me know!

3.2.4 Parsing successful data
{"phoneNumber":"13880684012","purePhoneNumber":"13880684012","countryCode":"86","watermark":{"timestamp":1519460296,"appid":"wx6ede2086ee29a89f"}}
 

If such json data is parsed, it is successful.

3.2.5 Another solution
public class AES {
    public static final AES instance = new AES();

    public static boolean initialized = false;

   /**
     * AES 
     * @param content  
     * @return
     * @throws InvalidAlgorithmParameterException
     * @throws NoSuchProviderException
     */
    public byte[] decrypt(byte[] content, byte[] keyByte, byte[] ivByte) throws InvalidAlgorithmParameterException {
        initialize();
        try {
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding");
            Key sKeySpec = new SecretKeySpec(keyByte, "AES");

            cipher.init(Cipher.DECRYPT_MODE, sKeySpec, generateIV(ivByte));// 
            byte[] result = cipher.doFinal(content);
            return result;
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (NoSuchPaddingException e) {
            e.printStackTrace();
        } catch (InvalidKeyException e) {
            e.printStackTrace();
        } catch (IllegalBlockSizeException e) {
            e.printStackTrace();
        } catch (BadPaddingException e) {
            e.printStackTrace();
        } catch (NoSuchProviderException e) {
           //TODO Auto-generated catch block
            e.printStackTrace();
        } catch (Exception e) {
           //TODO Auto-generated catch block
            e.printStackTrace();
        }
        return null;
    }

    public static void initialize(){
        if (initialized) return;
        Security.addProvider(new BouncyCastleProvider());
        initialized = true;
    }

   //iv
    public static AlgorithmParameters generateIV(byte[] iv) throws Exception{
        AlgorithmParameters params = AlgorithmParameters.getInstance("AES");
        params.init(new IvParameterSpec(iv));
        return params;
    }
}
 

This will also have the above problems, sometimes it will fail to parse! The specific method is still being explored, there is a great god who knows the method and the original, hope to inform!

3.2.6 Third-party SDK method
WxMaPhoneNumberInfo phoneNoInfo = this.wxService.getUserService().getPhoneNoInfo(sessionKey, encryptedData, iv);
        phoneNumber = phoneNoInfo.getPurePhoneNumber();
 

This will also have the above problems, and sometimes the analysis will fail!

4. summary

1. The small terminal initiates a request and carries the main parameters

2. After receiving the/login request in the java background, call the WeChat interface according to the code to obtain the user's unique identifier openid and sessionKey

3. Query the mysql database according to openid to determine whether the user exists, if it does not exist, store the user's non-sensitive information and other initialization data in the database, if it already exists, do not operate

4. Query the redis database according to openid to determine whether the skey corresponding to openid exists, and if it exists, delete the old skey and the corresponding openid and sessionKey

5. Generate a unique skey through uuid, use openid as the key and skey as the value, and store it in redis

6. Then use skey as the key, and store the json string of openid and sessionKey as the value in redis.

7. According to the decryption algorithm, the parameters are encryptedData, sessionKey and iv, to obtain user information userInfo, if the userInfo field does not meet the needs, you can add the required fields and values through userInfo.put("balance",user.getUbalance());

8. Encapsulate the data required by the WeChat applet into the map and return it to the applet.

Reference material (thanks)
  • https://blog.csdn.net/abcwanglinyong/article/details/80267901
  • https://github.com/binarywang/weixin-java-miniapp-demo
  • https://www.cnblogs.com/nosqlcoco/p/6105749.html
  • https://www.cnblogs.com/suxiaoqi/p/7874635.html