Maison  >  Article  >  Java  >  Comment implémenter le processus de connexion Java de l'applet WeChat

Comment implémenter le processus de connexion Java de l'applet WeChat

WBOY
WBOYavant
2023-05-22 22:40:041138parcourir

小program客户端

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{
 
    }
  }
});
}
复制代码

L'applet WeChat initie une demande de connexion, et les paramètres portés sont principalement :

code:loginRes.code,//临时登录凭证
    rawData:infoRes.rawData,//用户非敏感信息
    signature:infoRes.signature,//签名
    encrypteData:infoRes.encryptedData,//用户敏感信息
    iv:infoRes.iv//解密算法的向量
复制代码

Explication du paramètre : code :loginRes.code,//Identifiants de connexion temporaires : doivent être transmis, le code est utilisé en échange de la sessionKey et de l'openId d'arrière-plan rawData :infoRes.rawData,//Signature d'informations non sensibles de l'utilisateur :infoRes.signature,/ /Signature encrypteData : infoRes.encryptedData,//Informations sensibles de l'utilisateur iv:infoRes.iv//Vecteur d'algorithme de décryptage

signature,//Signature, Données chiffrées,//Informations sensibles de l'utilisateur, iv //Vecteur d'algorithme de décryptage :

Ces trois paramètres sont utilisés pour décoder les informations sensibles des utilisateurs, telles que les numéros de téléphone et d'autres informations.

Les données requises comprennent principalement : la clé, utilisée pour marquer le caractère unique de l'utilisateur.

3. Backend Java

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

Analysons le code ci-dessus :

3.1 Obtenez openid et sessionKey
session = this.wxService.getUserService().getSessionInfo(code);

//获取到openid和sessionkey
openid = session.getOpenid();
sessionKey = session.getSessionKey();
复制代码

Ce code est-il très concis ? Un SDK tiers (weixin-java-tools) est utilisé ici. Grâce à ce SDK, openid et sessionKey peuvent être obtenus très facilement, comme le montre la démo détaillée.

Bien sûr, si vous ne souhaitez pas utiliser de SDK tiers, vous pouvez également l'implémenter vous-même. Le code d'implémentation est le suivant :

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 Décrypter les données sensibles de l'utilisateur pour obtenir des informations sur l'utilisateur
3.2.1contrôleur

Cette partie a rencontré de nombreux pièges. Parce qu'elle doit obtenir le numéro de téléphone mobile de l'utilisateur, elle doit décrypter les informations de l'utilisateur.

/**
     * 获取用户板绑定的手机号
     * @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.2classe d'outils de décryptage

La classe d'outils WxMiniappUtils.decrypt est appelée ici. La classe d'outils est la suivante :

/**
     * 解密用户手机号算法
     * @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);
    }
复制代码

. La classe Cipher utilisée ici est la classe javax.crypto.

3.2.3 Question

Mais en utilisant cette classe d'outils de décryptage ici, j'ai rencontré beaucoup de problèmes.

Premièrement : le décryptage AES signale une erreur javax.crypto.BadPaddingException : bloc de tampon corrompu

Ce problème est dû à la classe d'outils utilisant Cipher.getInstance("AES/CBC/PKCS5Padding").

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

Deuxièmement : java.security.InvalidAlgorithmParameterException : mauvaise longueur IV : doit être 16 Ce problème est dû au fait que le iv décodé n'est pas 16 bits, il semble être 15 bits, je ne sais pas pourquoi.

Solution : Comment résoudre ce problème ? Je n'ai pas trouvé de solution moi-même. Si quelqu'un a une solution, faites-le-moi savoir !

Ma solution : En fait, j'ai trouvé que ce problème n'est pas un problème avec cette classe d'outils. Après une journée de lutte, j'ai découvert que cette classe d'outils n'est pas incapable de décoder les numéros de téléphone portable. et certains ne peuvent pas analyser les numéros de téléphone mobile. Il n'y en a que des informations ordinaires, donc je pense que cela peut être dû au fait que l'utilisateur de WeChat s'est enregistré avec un numéro de téléphone mobile, donc certains peuvent être analysés et d'autres ne peuvent pas être analysés. Si quelqu'un a d'autres méthodes, n'hésitez pas à me le faire savoir !

3.2.4 Analyser les données réussies
{"phoneNumber":"13880684012","purePhoneNumber":"13880684012","countryCode":"86","watermark":{"timestamp":1519460296,"appid":"wx6ede2086ee29a89f"}}
复制代码

Si ces données JSON sont analysées, cela signifie que l'opération a réussi.

3.2.5 Une autre 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 Méthode SDK tierce
WxMaPhoneNumberInfo phoneNoInfo = this.wxService.getUserService().getPhoneNoInfo(sessionKey, encryptedData, iv);
        phoneNumber = phoneNoInfo.getPurePhoneNumber();
复制代码

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Cet article est reproduit dans:. en cas de violation, veuillez contacter admin@php.cn Supprimer