Home  >  Article  >  Java  >  How to implement the Java login process of WeChat applet

How to implement the Java login process of WeChat applet

WBOY
WBOYforward
2023-05-22 22:40:041194browse

小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 parameters carried are mainly:

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, code is used to exchange for the background sessionKey and openId rawData:infoRes.rawData,//User non-sensitive information signature:infoRes.signature,//Signature encrypteData: infoRes.encryptedData,//User sensitive information iv:infoRes.iv//Decryption algorithm vector

signature,//Signature, encryptedData,//User sensitive information, iv //Decryption algorithm vector:

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

The required data mainly include: skey, used to mark the uniqueness of the user.

3. Java backend

/**
     * 登陆接口
     */
    @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;
    }
复制代码

Let’s analyze 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, openid and sessionKey can be obtained very easily, as shown in the detailed demo.

Of course, if you don’t want to use a third-party 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 many pitfalls. 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

The tool class WxMiniappUtils.decrypt 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);
    }
复制代码

The Cipher class used here is the javax.crypto class.

3.2.3 Question

But when using this decrypt tool class here, I encountered a lot of problems.

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

This problem is due to the tool class using 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 problem? I haven’t found a solution myself. If anyone has a solution, please let me know!

My solution: In fact, I found that this problem is not a problem with this tool class. After struggling for a day, I found that this tool class is not unable to decode mobile phone numbers. Some are able to decode mobile phone numbers, and some cannot parse mobile phone numbers. There are only ordinary ones. Information, so I think this may be due to whether the WeChat user registered with a mobile phone number, so some can be parsed and some cannot. If anyone has other methods, 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 means 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;
    }
}
复制代码
3.2.6 Third-party sdk method
WxMaPhoneNumberInfo phoneNoInfo = this.wxService.getUserService().getPhoneNoInfo(sessionKey, encryptedData, iv);
        phoneNumber = phoneNoInfo.getPurePhoneNumber();
复制代码

The above is the detailed content of How to implement the Java login process of WeChat applet. For more information, please follow other related articles on the PHP Chinese website!

Statement:
This article is reproduced at:yisu.com. If there is any infringement, please contact admin@php.cn delete