新版:前端要授权两次,一次获取用户信息授权码code,另外一次获取用户手机授权码code,全部传给后端。后端通过用户信息授权码获取openid,通过手机授权码获取手机号码。老版:前端传给后端授权码code和用户手机授权回调里的iv和encryptedData给后端,后端通过code获取openid和sessionKey,然后他用sessionKey和iv解密encryptedData获取手机号。最后通过手机号进行绑定用户,然后通过登录验证返回给前端登录凭证token。

登录逻辑

新版

1.调用uni.login()获取code1

2.用户主动触发button按钮在回调getPhoneNumber获取code2

Page({getPhoneNumber (e) {console.log(e.detail.code)}})

3.后端拿到code1获取openid,code2获取手机号码(代码在api里面)

老版

1.先在onshow()生命周期中获取code

2.用户主动触发button按钮在回调getPhoneNumber获取iv和encryptedData

Page({getPhoneNumber (e) {console.log(e.detail.errMsg)console.log(e.detail.iv)console.log(e.detail.encryptedData)}})

3.后端拿到code、iv、encryptedData,然后code获取openid和sessionKey,然后通过sessionKey和iv解密encryptedData获取到手机号

获取得到的解密数据为以下 json 结构:{"phoneNumber": "13580006666","purePhoneNumber": "13580006666","countryCode": "86","watermark":{"appid":"APPID","timestamp": TIMESTAMP}}

解密工具类

package hry.project.cdwjs.wxLogin;import org.apache.commons.codec.binary.Base64;import org.bouncycastle.jce.provider.BouncyCastleProvider;import javax.crypto.Cipher;import javax.crypto.spec.IvParameterSpec;import javax.crypto.spec.SecretKeySpec;import java.nio.charset.StandardCharsets;import java.security.*;/** * @author qyb * @version 1.0 * @date 2023/3/13-9:38 */public class WxDecryptUtills {public static boolean initialized = false;/** * Adds a provider to the next position available. */public static void initialize() {if (initialized) return;// Construct a new provider.This should only be required when// using runtime registration of the provider using theSecurity.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;}/** * AES解密 * @param content 密文 * @param keyByte sessionKey * @param ivByte iv * @return 解密json数据 */public static byte[] decrypt(byte[] content, byte[] keyByte, byte[] ivByte){initialize();try {Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding");Key sKeySpec = new SecretKeySpec(keyByte, "AES");// cipher 初始化cipher.init(Cipher.DECRYPT_MODE, sKeySpec, generateIV(ivByte));return cipher.doFinal(content);} catch (Exception e) {e.printStackTrace();}return null;}/** * 微信小程序用户信息解密 * @param encryptedData 加密数据 * @param sessionKey会话密钥 * @param iv向量 * @return {@link String} */public static String decrypt(String encryptedData, String sessionKey, String iv){try {byte[] resultByte = decrypt(Base64.decodeBase64(encryptedData), Base64.decodeBase64(sessionKey), Base64.decodeBase64(iv));if(null != resultByte && resultByte.length > 0){return new String(resultByte, StandardCharsets.UTF_8);}} catch (Exception e) {e.printStackTrace();}return null;}}

api

package hry.project.cdwjs.wxLogin.impl;import com.alibaba.fastjson.JSON;import com.alibaba.fastjson.JSONObject;import hry.bean.JsonResult;import hry.business.cu.model.CuCustomer;import hry.business.cu.service.CuCustomerService;import hry.project.cdwjs.wxLogin.WxDecryptUtills;import hry.project.cdwjs.wxLogin.WxLoginService;import hry.project.cdwjs.wxLogin.WxLoginVo;import hry.redis.RedisService;import hry.security.jwt.JWTToken;import hry.security.jwt.JWTUtil;import hry.utils.HttpUtils;import lombok.extern.slf4j.Slf4j;import org.apache.commons.lang3.StringUtils;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.beans.factory.annotation.Value;import org.springframework.stereotype.Service;import java.util.HashMap;/** * @author qyb * @version 1.0 * @date 2023/3/9-17:38 */@Service@Slf4jpublic class WxLoginServiceImpl implements WxLoginService {@Value("${wxLogin.appId}")private String appId;@Value("${wxLogin.appSecret}")private String appSecret;@Autowiredprivate CuCustomerService cuCustomerService;@Autowiredprivate RedisService redisService;/** * 获取accesstoken * * @return */private String getAccessToken() {String accessToken = "";String url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=" + appId + "&secret=" + appSecret;try {String resultString = HttpUtils.get(url);log.info("获取微信accessToken:{}", resultString);if (StringUtils.isNotEmpty(resultString)) {JSONObject jsonObject = JSON.parseObject(resultString);accessToken = jsonObject.get("access_token").toString();} else {log.error("返回值为空,请检查请求报文或者请求地址是否正确");}} catch (Exception e) {e.printStackTrace();}return accessToken;}/** * 获取手机号 */private String getPhoneNumber(String code) {String phoneNumber = "";String url = "https://api.weixin.qq.com/wxa/business/getuserphonenumber?access_token=" + getAccessToken();HashMap params = new HashMap();params.put("code", code);try {String resultString = HttpUtils.postByQuery(url, params, null);log.info("获取微信手机号码:{}", resultString);if (StringUtils.isNotEmpty(resultString)) {JSONObject jsonObject = JSON.parseObject(resultString);JSONObject phone_info = jsonObject.getJSONObject("phone_info");phoneNumber = phone_info.getString("phoneNumber");} else {log.error("返回值为空,请检查请求报文或者请求地址是否正确");}} catch (Exception e) {e.printStackTrace();}return phoneNumber;}/** * 获取openId */private String getOpenId(String code) {String url = "https://api.weixin.qq.com/sns/jscode2session?appid=" + appId + "&secret=" + appSecret + "&js_code=" + code + "&grant_type=authorization_code";try {String resultString = HttpUtils.get(url);log.info("获取微信openId:{}", resultString);if (StringUtils.isNotEmpty(resultString)) {return resultString;} else {log.error("返回值为空,请检查请求报文或者请求地址是否正确");}} catch (Exception e) {e.printStackTrace();}return "";}@Overridepublic JsonResult loginByWx(WxLoginVo wxLoginVo) {String res = this.getOpenId(wxLoginVo.getUserInfoCode());JSONObject jsonObject = JSONObject.parseObject(res);String openId = jsonObject.getString("openid");String sessionKey = jsonObject.getString("session_key");if (StringUtils.isEmpty(openId)) {return new JsonResult().setMsg("未获取到openId,登录失败");}String data = WxDecryptUtills.decrypt(wxLoginVo.getEncryptData(), sessionKey, wxLoginVo.getIv());JSONObject jsonObject1 = JSONObject.parseObject(data);String phoneNumber =jsonObject1.getString("phoneNumber");if (StringUtils.isEmpty(phoneNumber)) {return new JsonResult().setMsg("未获取到手机号,登录失败");}CuCustomer cuCustomer = cuCustomerService.checkMobile(phoneNumber);if (cuCustomer == null) {//注册cuCustomer = cuCustomerService.regist3(openId, phoneNumber, wxLoginVo.getNickname(), wxLoginVo.getAvatar());} else {//写入微信openidif (StringUtils.isEmpty(cuCustomer.getWxOpenId())) {cuCustomer.setWxOpenId(openId);}cuCustomer.setWxAvatar(wxLoginVo.getAvatar());cuCustomerService.update(cuCustomer);}//登录String token = JWTUtil.sign(phoneNumber, JWTToken.SOURCE_PC, JWTToken.TYPE_CUSTOMER, cuCustomer.getPassword());redisService.save(JWTUtil.getCustomerRefreshTimeKey(token), JSON.toJSONString(cuCustomer), JWTUtil.REFRESH_TIME);redisService.save(JWTUtil.getCustomerUserKey(token), JSON.toJSONString(cuCustomer), JWTUtil.EXPIRE_TIME);//防止用户多端登录,产生多个tokenString oldTokenStr = redisService.get("LOGINCUCUSTOMER:" + cuCustomer.getId());if (StringUtils.isNotEmpty(oldTokenStr)) {JWTToken oldToken = new JWTToken(oldTokenStr);redisService.delete("JWT:token:" + oldToken.getSource() + ":" + oldToken.getType() + ":refreshTime:" + oldToken.getSignId());redisService.delete("JWT:token:" + oldToken.getSource() + ":" + oldToken.getType() + ":user:" + oldToken.getSignId());}redisService.save("LOGINCUCUSTOMER:" + cuCustomer.getId(), token);HashMap map = new HashMap();map.put("token", token);return new JsonResult().setSuccess(true).setObj(map);}}

注意:老版调用过程中一定要先调用uni.login(),再去触发button获取手机号,不然会导致sessionKey失效,从而使得后端解密失败。

微信小程序文档