diff --git a/pom.xml b/pom.xml index 7a58dae1..53c70268 100644 --- a/pom.xml +++ b/pom.xml @@ -20,6 +20,7 @@ signin common + wechatutil diff --git a/signin/pom.xml b/signin/pom.xml index 392f33fd..db5fc614 100644 --- a/signin/pom.xml +++ b/signin/pom.xml @@ -27,6 +27,13 @@ 1.0-SNAPSHOT + + + wechatutil + com.ccsens + 1.0-SNAPSHOT + + diff --git a/signin/src/main/java/com/ccsens/signin/api/UserController.java b/signin/src/main/java/com/ccsens/signin/api/UserController.java index 422c7428..ce62e0c0 100644 --- a/signin/src/main/java/com/ccsens/signin/api/UserController.java +++ b/signin/src/main/java/com/ccsens/signin/api/UserController.java @@ -222,8 +222,11 @@ public class UserController { }) @RequestMapping(value="/bindingNoCode",method = RequestMethod.POST,produces = {"application/json;charset=UTF-8"}) public JsonResponse bindingPhoneNoCode(HttpServletRequest request, - @ApiParam @RequestBody UserDto.WxBindingPhone wxPhone) throws Exception { + @ApiParam @RequestBody UserDto.WxBindingPhone2 wxPhone) throws Exception { Long currentUserId = Long.valueOf(((Claims) request.getAttribute(WebConstant.REQUEST_KEY_CLAIMS)).getSubject()); + + + UserVo.UserSign userSignVo = userService.bindingPhoneNoCode(currentUserId,wxPhone); UserVo.TokenBean tokenBean = null; diff --git a/signin/src/main/java/com/ccsens/signin/bean/dto/UserDto.java b/signin/src/main/java/com/ccsens/signin/bean/dto/UserDto.java index 2eb199cf..26ff2525 100644 --- a/signin/src/main/java/com/ccsens/signin/bean/dto/UserDto.java +++ b/signin/src/main/java/com/ccsens/signin/bean/dto/UserDto.java @@ -142,6 +142,20 @@ public class UserDto { private String smsCode; } + @Data + @ApiModel + public static class WxBindingPhone2{ + @NotEmpty + @ApiModelProperty("加密数据") + private String encryptedData; + @NotEmpty + @ApiModelProperty("iv") + private String iv; + @NotEmpty + @ApiModelProperty("小程序类型 tall:tall basicCar:暴风眼 SP:赛跑 SQ:数钱 BH:拔河") + private String miniType; + } + @Data @ApiModel public static class WxInfo{ diff --git a/signin/src/main/java/com/ccsens/signin/service/IUserService.java b/signin/src/main/java/com/ccsens/signin/service/IUserService.java index 95ee5eb8..81676015 100644 --- a/signin/src/main/java/com/ccsens/signin/service/IUserService.java +++ b/signin/src/main/java/com/ccsens/signin/service/IUserService.java @@ -105,8 +105,8 @@ public interface IUserService { /** * 绑定手机号不用验证码 * @param currentUserId userId - * @param wxPhone 手机号 + * @param wxPhone 加密数据 * @return */ - UserVo.UserSign bindingPhoneNoCode(Long currentUserId, UserDto.WxBindingPhone wxPhone); + UserVo.UserSign bindingPhoneNoCode(Long currentUserId, UserDto.WxBindingPhone2 wxPhone); } diff --git a/signin/src/main/java/com/ccsens/signin/service/UserService.java b/signin/src/main/java/com/ccsens/signin/service/UserService.java index febab0ba..125aa4e8 100644 --- a/signin/src/main/java/com/ccsens/signin/service/UserService.java +++ b/signin/src/main/java/com/ccsens/signin/service/UserService.java @@ -24,6 +24,8 @@ import com.ccsens.util.enterprisewx.vo.WeiXinVo; import com.ccsens.util.exception.BaseException; import com.ccsens.util.wx.WxGzhUtil; import com.ccsens.util.wx.WxXcxUtil; +import com.ccsens.wechatutil.bean.po.WxPhoneDecryptInfo; +import com.ccsens.wechatutil.wxmini.MiniEncryptionAndDecryptionUtil; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.RandomStringUtils; import org.springframework.stereotype.Service; @@ -346,12 +348,18 @@ public class UserService implements IUserService { //0.获取openid Long start = System.currentTimeMillis(); WxXcxUtil.WechatUser wechatUser = WxXcxUtil.getUserInfo(code, gameType); + log.info("微信登录:{}", wechatUser); Long end = System.currentTimeMillis(); log.info("调用微信查询openId耗时:{}", end - start); String openId = wechatUser.openid; String unionId = wechatUser.unionid; log.info("小程序登录,openid:{} ,unionId:{}", openId, unionId); - return getUserSign(openId, unionId, (byte) WebConstant.IDENTIFY_TYPE.Wxmp.value, null); + UserVo.UserSign sign = getUserSign(openId, unionId, (byte) WebConstant.IDENTIFY_TYPE.Wxmp.value, null); + if (sign == null) { + return null; + } + redisUtil.set(StrUtil.format(WebConstant.Wx.SESSION_KEY, sign.getUserId(), gameType), wechatUser.session_key); + return sign; } @@ -818,11 +826,11 @@ public class UserService implements IUserService { * 绑定手机号(不用验证码) * * @param currentUserId userId - * @param wxPhone 手机号 + * @param wxPhone 手机号(加密) * @return 用户id和认证类型 */ @Override - public UserVo.UserSign bindingPhoneNoCode(Long currentUserId, UserDto.WxBindingPhone wxPhone) { + public UserVo.UserSign bindingPhoneNoCode(Long currentUserId, UserDto.WxBindingPhone2 wxPhone) { UserVo.UserSign userSignVo; //查找该用户以前绑定的手机 SysAuthExample authExample = new SysAuthExample(); @@ -832,11 +840,25 @@ public class UserService implements IUserService { if (CollectionUtil.isNotEmpty(authList)) { throw new BaseException(CodeEnum.ALREADY_BINDING_PHONE); } else { + // 解密 + Object sessionKey = redisUtil.get(StrUtil.format(WebConstant.Wx.SESSION_KEY, currentUserId, wxPhone.getMiniType())); + if (ObjectUtil.isNull(sessionKey)) { + throw new BaseException(CodeEnum.PARAM_ERROR); + } + String decryption = MiniEncryptionAndDecryptionUtil.decryption(wxPhone.getEncryptedData(), (String) sessionKey, wxPhone.getIv()); + if (StrUtil.isEmpty(decryption)) { + throw new BaseException(CodeEnum.DATA_DECRYPTION); + } + WxPhoneDecryptInfo wxPhoneDecryptInfo = JSONObject.parseObject(decryption, WxPhoneDecryptInfo.class); + String phone = wxPhoneDecryptInfo.getPhoneNumber(); + if (StrUtil.isEmpty(phone)) { + throw new BaseException(CodeEnum.WBS_NOT_PHONE); + } //改手机对应账户,如果有,提示 List phoneList; SysAuthExample phoneExample = new SysAuthExample(); phoneExample.createCriteria().andIdentifyTypeEqualTo((byte) WebConstant.IDENTIFY_TYPE.Phone.value) - .andIdentifierEqualTo(wxPhone.getPhone()); + .andIdentifierEqualTo(phone); phoneList = authDao.selectByExample(phoneExample); if (CollectionUtil.isNotEmpty(phoneList)) { throw new BaseException(CodeEnum.MERGE_WX_PHONE); @@ -846,11 +868,11 @@ public class UserService implements IUserService { auth.setId(snowflake.nextId()); auth.setUserId(currentUserId); auth.setIdentifyType((byte) WebConstant.IDENTIFY_TYPE.Phone.value); - auth.setIdentifier(wxPhone.getPhone()); + auth.setIdentifier(phone); authDao.insertSelective(auth); //给所有手机号一样的角色添加userId - relevanceUserService.relevancePhone(wxPhone.getPhone(), currentUserId); + relevanceUserService.relevancePhone(phone, currentUserId); //返回值 userSignVo = new UserVo.UserSign(); userSignVo.setAuthId(auth.getId()); diff --git a/signin/src/main/resources/application.yml b/signin/src/main/resources/application.yml index b5408a3a..e75cc2c3 100644 --- a/signin/src/main/resources/application.yml +++ b/signin/src/main/resources/application.yml @@ -1,5 +1,5 @@ spring: profiles: - active: dev - include: util-dev,common + active: prod + include: util-prod,common diff --git a/util/src/main/java/com/ccsens/util/CodeEnum.java b/util/src/main/java/com/ccsens/util/CodeEnum.java index 6b765978..5135524c 100644 --- a/util/src/main/java/com/ccsens/util/CodeEnum.java +++ b/util/src/main/java/com/ccsens/util/CodeEnum.java @@ -221,6 +221,7 @@ public enum CodeEnum { //TALL3 PROJECT_REGION_NO_SAME(181,"项目域不同无法进行操作",true), NO_POWER(182,"权限不足",true), + DATA_DECRYPTION(183,"解密失败,数据可能遭受到破坏,操作取消。",true), ; diff --git a/util/src/main/java/com/ccsens/util/WebConstant.java b/util/src/main/java/com/ccsens/util/WebConstant.java index e6899733..c3efbef0 100644 --- a/util/src/main/java/com/ccsens/util/WebConstant.java +++ b/util/src/main/java/com/ccsens/util/WebConstant.java @@ -46,6 +46,8 @@ public class WebConstant { return String.format(GZH_AUTH_URL,appId, URLUtil.encode(url),wxGzhAuthType.getText()); } + public static final String SESSION_KEY = "{}_{}_session_key"; + } public enum WxGzhAuthType { diff --git a/util/src/test/java/com/ccsens/util/Base64Test.java b/util/src/test/java/com/ccsens/util/Base64Test.java index 522082c2..137e7ecb 100644 --- a/util/src/test/java/com/ccsens/util/Base64Test.java +++ b/util/src/test/java/com/ccsens/util/Base64Test.java @@ -9,6 +9,7 @@ import com.alibaba.fastjson.JSONObject; import com.ccsens.util.exception.BaseException; import io.swagger.annotations.ApiModelProperty; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.codec.binary.Hex; import org.junit.Test; import sun.misc.BASE64Decoder; import sun.misc.BASE64Encoder; @@ -18,6 +19,7 @@ import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; /** * @description: @@ -71,6 +73,19 @@ public class Base64Test { throw new BaseException("ssss"); } + } + + @Test + public void test04() throws NoSuchAlgorithmException { + String mingWen = "{\"nickName\":\"Band\",\"gender\":1,\"language\":\"zh_CN\",\"city\":\"Guangzhou\",\"province\":\"Guangdong\",\"country\":\"CN\",\"avatarUrl\":\"http://wx.qlogo.cn/mmopen/vi_32/1vZvI39NWFQ9XM4LtQpFrQJ1xlgZxx3w7bQxKARol6503Iuswjjn6nIGBiaycAjAtpujxyzYsrztuuICqIM5ibXQ/0\"}HyVFkGl5F5OQWJZZaNzBBg=="; + String sha1 = "75e81ceda165f4ffa64f4068af58c64b8f54b88c"; + MessageDigest digest = MessageDigest.getInstance("SHA"); + digest.update(mingWen.getBytes()); + String result = Hex.encodeHexString(digest.digest()); + System.out.println("==========" + (sha1.equals(result))); + + + } /** diff --git a/wechatutil/src/main/java/com/ccsens/wechatutil/api/DebugController.java b/wechatutil/src/main/java/com/ccsens/wechatutil/api/DebugController.java index 9672c3c4..edeaa8af 100644 --- a/wechatutil/src/main/java/com/ccsens/wechatutil/api/DebugController.java +++ b/wechatutil/src/main/java/com/ccsens/wechatutil/api/DebugController.java @@ -60,7 +60,7 @@ public class DebugController { }) @RequestMapping(value="message",method = RequestMethod.GET,produces = {"application/json;charset=UTF-8"}) public JsonResponse debugWxMessage(HttpServletRequest request) throws Exception { - OfficialAccountMessageUtil.officialMessage(); +// OfficialAccountMessageUtil.officialMessage(); log.info("发送公众号消息"); return JsonResponse.newInstance().ok("测试"); } diff --git a/wechatutil/src/main/java/com/ccsens/wechatutil/bean/po/WxPhoneDecryptInfo.java b/wechatutil/src/main/java/com/ccsens/wechatutil/bean/po/WxPhoneDecryptInfo.java new file mode 100644 index 00000000..005a535d --- /dev/null +++ b/wechatutil/src/main/java/com/ccsens/wechatutil/bean/po/WxPhoneDecryptInfo.java @@ -0,0 +1,27 @@ +package com.ccsens.wechatutil.bean.po; + +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +/** + * @description: + * @author: whj + * @time: 2021/9/27 10:59 + */ +@Data +public class WxPhoneDecryptInfo { + @ApiModelProperty("用户绑定的手机号(国外手机号会有区号)") + private String phoneNumber; + @ApiModelProperty("没有区号的手机号") + private String purePhoneNumber; + @ApiModelProperty("区号") + private int countryCode; + private WaterMark watermark; + + @Data + public static class WaterMark { + /** 单位:秒 */ + private Long timestamp; + private String appid; + } +} diff --git a/wechatutil/src/main/java/com/ccsens/wechatutil/wxmini/MiniEncryptionAndDecryptionUtil.java b/wechatutil/src/main/java/com/ccsens/wechatutil/wxmini/MiniEncryptionAndDecryptionUtil.java new file mode 100644 index 00000000..bc250191 --- /dev/null +++ b/wechatutil/src/main/java/com/ccsens/wechatutil/wxmini/MiniEncryptionAndDecryptionUtil.java @@ -0,0 +1,102 @@ +package com.ccsens.wechatutil.wxmini; + +import com.alibaba.fastjson.JSON; +import com.ccsens.wechatutil.bean.po.WxPhoneDecryptInfo; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.codec.binary.Hex; +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.*; +import java.security.spec.InvalidParameterSpecException; + +import org.apache.xmlbeans.impl.util.Base64; + +/** + * @description: + * @author: whj + * @time: 2021/9/27 10:47 + */ +@Slf4j +public class MiniEncryptionAndDecryptionUtil { + + private final static String sha = "SHA"; + /**加密方式*/ + private final static String keyAlgorithm = "AES"; + + + static { + Security.addProvider(new BouncyCastleProvider()); + } + + /** + * SHA1签名(获取手机号) + * @param data 原始数据 + * @return 签名 + * @throws + */ + public static String sha1(String data) { + log.info("签名原始数据:{}", data); + MessageDigest md; + try { + md = MessageDigest.getInstance(sha); + } catch (NoSuchAlgorithmException e) { + log.error("微信小程序签名异常:", e); + return null; + } + md.update(data.getBytes()); + String result = Hex.encodeHexString(md.digest()); + log.info("签名结果:{}", result); + return result; + } + + /** + * 解密(获取手机号) + * @param originalContent 待解密数据 + * @param sessionKey 会话密钥 + * @param iv 加密算法的初始向量 + * @return 解密数据 + */ + public static String decryption(String originalContent, String sessionKey, String iv ){ + try { + log.info("数据解密, original:{}, key:{}, iv:{}", originalContent, sessionKey, iv); + //数据填充方式 + Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding"); + Key sKeySpec = new SecretKeySpec(Base64.decode(sessionKey.getBytes()), keyAlgorithm); + // 初始化 + cipher.init(Cipher.DECRYPT_MODE, sKeySpec, generateIV(Base64.decode(iv.getBytes()))); + byte[]data = cipher.doFinal(Base64.decode(originalContent.getBytes())); + String result = new String(data, StandardCharsets.UTF_8); + log.info("微信小程序解密数据:{}", result); + return result; + } catch (Exception e) { + log.error("解密异常:", e); + return null; + } + } + + /** + * 生成iv + * @param iv iv + * @return iv对象 + * @throws NoSuchAlgorithmException 没有签名 + * @throws InvalidParameterSpecException 无效参数 + */ + private static AlgorithmParameters generateIV(byte[] iv) throws NoSuchAlgorithmException, InvalidParameterSpecException { + AlgorithmParameters params = AlgorithmParameters.getInstance(keyAlgorithm); + params.init(new IvParameterSpec(iv)); + return params; + } + + public static void main(String[] args) { + String content = "aH7vLxzu3upsT8kK5DbjCbsIoOkTKIKUSNruHbhmmH6km/Yeb1eCsdfFY4HNPeNZH0oBRGmQ7sn8c/PaaYlLfi9598ghEvfl3HENWSLl43MqNXEFvCtTzxhc0Y7dJRtKCIWL4EEr8M42XxnhH77lAmpLomL+fbzw8upmz+gcJWqYMnXPKtGH2B2BeHC/njUgeuAeTqR7zuYihPyVY4Ol8g=="; + String iv = "eW1+fg0f3TOU25MQfvTDwg=="; + String sessionKey = "gA+NNouMd0FMmaf3LkQrMA=="; + decryption(content, sessionKey, iv); + + } + +}