2025-10-16 11:40:07 +08:00

324 lines
13 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package com.cdzy.user.utils;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.cdp.product.security.context.SecurityContext;
import com.cdp.product.security.exception.DecryptFailureException;
import com.cdp.product.security.exception.EncryptFailureException;
import com.cdp.product.security.exception.SignFailureException;
import com.cdp.product.security.strategyImpl.SM2StrategyImpl;
import com.cdzy.common.model.response.JsonResult;
import com.cdzy.common.secure.SecureComponent;
import com.cdzy.common.secure.SecureSM2Component;
import com.cdzy.user.config.RealNameVerifyConfig;
import com.cdzy.user.config.WechatConfig;
import com.cdzy.user.enums.RealNameAttestationStatus;
import com.cdzy.user.model.dto.UserValidateDto;
import com.cdzy.user.model.entity.EbikeUser;
import com.cdzy.user.model.entity.EbikeUserRealInfo;
import com.cdzy.user.service.EbikeUserRealInfoService;
import com.cdzy.user.service.EbikeUserService;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import okhttp3.*;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.springframework.stereotype.Service;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.time.LocalDateTime;
import java.util.*;
/**
* 验证工具类
*
* @author dingchao
* @date 2025/4/9
* @modified by:
*/
@Slf4j
@Service
public class VerifyUtil {
/**
* 微信登录API地址
*/
private static final String WECHAT_LOGIN_URL = "https://api.weixin.qq.com/sns/jscode2session";
/**
* 数据宝实名认证API地址
*/
private static final String REALNAME_VERIFY_URL = "https://api.chinadatapay.com/communication/personal/1882";
/**
* OkHttpClient实例
*/
private OkHttpClient client = null;
@Resource
private WechatConfig wechatConfig;
@Resource
private RealNameVerifyConfig realNameVerifyConfig;
@Resource
private EbikeUserService ebikeUserService;
@Resource
private EbikeUserRealInfoService ebikeUserRealInfoService;
//暂时硬编码后续放到redis或者config中【本地数据加密解密】
private SecureComponent secureComponent;
private static final String privateKey = "f8209a2ebe6691e41e1f2b667bfe71f0b511716cc0f7c4452502fc12ec3957e4";
private static final String publicKey = "04f5084ee12767d932f293508e30e3b0100185042ec0f061dedaf92b793b93f79fd6179d5e47e25b7aec98e00cf90dd56df1f8191012537187e7bbfd2d1de299fc";
// 加解密上下⽂
private final SecurityContext securityContext;
/**
* 微信工具类构造函数。
*/
public VerifyUtil() throws IOException {
client = new OkHttpClient();
securityContext = new SecurityContext();
//设置加解密上下⽂类调⽤SM2加解密类
securityContext.setSecurityImpl(new SM2StrategyImpl());
//设置加解密上下⽂类调⽤SM2加解密类加签
securityContext.setSign(new SM2StrategyImpl());
}
/**
* 获取openId。
*
* @param code 微信登录返回的code
* @return openId
*/
public JSONObject wechatAuthority(String code) {
Map<String, String> params = new HashMap<>();
params.put("appid", wechatConfig.getAppId());
params.put("secret", wechatConfig.getAppSecret());
params.put("js_code", code);
params.put("grant_type", "authorization_code");
return httpGet("获取openId", WECHAT_LOGIN_URL, params);
}
/**
* 解密微信用户信息。
*
* @param encryptedData 加密数据
* @param sessionKey 会话密钥
* @param iv 初始向量
* @return 解密后的用户信息
*/
public String decryptData(String encryptedData, String sessionKey, String iv) {
try {
// 添加 BouncyCastle 安全提供器
Security.addProvider(new BouncyCastleProvider());
// Base64 解码参数
byte[] encryptedBytes = Base64.getDecoder().decode(encryptedData);
byte[] sessionKeyBytes = Base64.getDecoder().decode(sessionKey);
byte[] ivBytes = Base64.getDecoder().decode(iv);
// 初始化 AES-CBC 解密器
SecretKeySpec keySpec = new SecretKeySpec(sessionKeyBytes, "AES");
IvParameterSpec ivSpec = new IvParameterSpec(ivBytes);
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding", "BC");
cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
// 执行解密
byte[] decryptedBytes = cipher.doFinal(encryptedBytes);
return new String(decryptedBytes, StandardCharsets.UTF_8);
} catch (Exception e) {
log.error("解密失败", e);
return null;
}
}
/**
* 验证用户实名
*
* @param userValidateDto 验证用户实名请求
* @return 验证结果
*/
public JsonResult<?> verifyRealName(UserValidateDto userValidateDto) {
EbikeUser ebikeUser = ebikeUserService.getById(userValidateDto.getUserId());
if (ebikeUser == null) {
log.error("用户不存在");
return JsonResult.failed("用户不存在");
}
try {
// 1. 加密数据
Map<String, String> params = new HashMap<>();
params.put("name", securityContext.encrypt(userValidateDto.getName(), realNameVerifyConfig.getServerPublicKey()));
params.put("idcard", securityContext.encrypt(userValidateDto.getIdCard(), realNameVerifyConfig.getServerPublicKey()));
params.put("timestamp", securityContext.encrypt(String.valueOf(System.currentTimeMillis()), realNameVerifyConfig.getServerPublicKey()));
String sign = securityContext.sign(params, realNameVerifyConfig.getClientPrivateKey());
params.put("sign", sign);
params.put("key", realNameVerifyConfig.getApiKey());
// 2. 验证用户实名
JSONObject result = httpPost("验证用户实名", REALNAME_VERIFY_URL, params, null);
log.info("验证用户实名结果:{}", result);
if (result == null) {
log.error("验证用户实名失败");
return JsonResult.failed("验证用户实名失败");
}
if ("10000".equals(result.getString("code"))) {
// 3. 解密结果
String data = securityContext.decrypt(result.getString("data"), realNameVerifyConfig.getClientPrivateKey());
JSONObject jsonData = JSON.parseObject(data);
if ("1".equals(jsonData.getString("result"))) {
// 4. 更新用户实名信息
updateRealNameInfo(userValidateDto, true);
return JsonResult.success("验证用户实名成功");
}
}
updateRealNameInfo(userValidateDto, false);
log.error("验证用户实名失败,{} {}", result.getString("code"), result.getString("message"));
return JsonResult.failed(result.getString("code") + ", " + result.getString("message"));
} catch (EncryptFailureException e) {
log.error("加密失败", e);
return JsonResult.failed("加密失败");
} catch (SignFailureException e) {
log.error("签名失败", e);
return JsonResult.failed("签名失败");
} catch (DecryptFailureException e) {
log.error("结果解密失败", e);
return JsonResult.failed("结果解密失败");
}
}
/**
* 更新用户实名信息
*
* @param userValidateDto 用户验证信息
* @param isValid 是否通过验证
*/
private void updateRealNameInfo(UserValidateDto userValidateDto, boolean isValid) {
EbikeUserRealInfo ebikeUserRealinfo = ebikeUserRealInfoService.getRealInfoByUserId(userValidateDto.getUserId());
String realName = userValidateDto.getName();
String idCard = userValidateDto.getIdCard();
if (ebikeUserRealinfo == null) {
ebikeUserRealinfo = new EbikeUserRealInfo();
ebikeUserRealinfo.setUserId(userValidateDto.getUserId());
ebikeUserRealinfo.setUserRealName(getSecureComponent().encrypt(realName, publicKey));
ebikeUserRealinfo.setUserIdCard(getSecureComponent().encrypt(idCard, publicKey));
ebikeUserRealinfo.setAttestationStatus(isValid ? RealNameAttestationStatus.CERTIFIED : RealNameAttestationStatus.PENDING_REVIEW);
ebikeUserRealinfo.setAuditTime(LocalDateTime.now());
ebikeUserRealInfoService.save(ebikeUserRealinfo);
} else {
ebikeUserRealinfo.setUserRealName(getSecureComponent().encrypt(realName, publicKey));
ebikeUserRealinfo.setUserIdCard(getSecureComponent().encrypt(idCard, publicKey));
ebikeUserRealinfo.setAttestationStatus(isValid ? RealNameAttestationStatus.CERTIFIED: RealNameAttestationStatus.PENDING_REVIEW);
ebikeUserRealinfo.setAuditTime(LocalDateTime.now());
ebikeUserRealInfoService.updateById(ebikeUserRealinfo);
}
}
/**
* 发送HTTP GET请求。
*
* @param func_ 功能描述
* @param url 请求URL
* @param params 请求参数
* @return 响应结果
*/
private JSONObject httpGet(String func_, String url, Map<String, String> params) {
StringJoiner paramJoiner = new StringJoiner("&");
for (Map.Entry<String, String> entry : params.entrySet()) {
paramJoiner.add(entry.getKey() + "=" + entry.getValue());
}
String requestUrl = url + "?" + paramJoiner.toString();
Request request = new Request.Builder()
.url(requestUrl)
.get()
.build();
try (Response response = client.newCall(request).execute()) {
if (response.isSuccessful()) {
if (response.body() != null) {
return JSON.parseObject(response.body().string());
}
log.error("{}, 请求{}无返回结果", func_, url);
return null;
}
if (response.body() != null) {
log.error("{}, 请求{}失败, {}", func_, url, response.body().string());
} else {
log.error("{}, 请求{}失败", func_, url);
}
return null;
} catch (Exception e) {
log.error("{}, 请求{}失败", func_, url, e);
return null;
}
}
/**
* 发送HTTP POST参数请求。
*
* @param func_ 功能描述
* @param url 请求URL
* @param params 请求参数
* @return 响应结果
*/
private JSONObject httpPost(String func_, String url, Map<String, String> params, Map<String, Object> body) {
StringJoiner paramJoiner = new StringJoiner("&");
for (Map.Entry<String, String> entry : params.entrySet()) {
paramJoiner.add(entry.getKey() + "=" + entry.getValue());
}
String requestUrl = url;
if (paramJoiner.length() > 0)
requestUrl += "?" + paramJoiner.toString();
Request.Builder builder = new Request.Builder()
.url(requestUrl);
if (body != null) {
MediaType typeJson = MediaType.parse("application/json; charset=utf-8");
RequestBody requestBody = RequestBody.create(JSON.toJSONString(body), typeJson);
builder.post(requestBody);
} else {
builder.post(RequestBody.create(null, new byte[0]));
}
Request request = builder.build();
try (Response response = client.newCall(request).execute()) {
if (response.isSuccessful()) {
if (response.body() != null) {
return JSON.parseObject(response.body().string());
}
log.error("{}, 请求{}无返回结果", func_, url);
return null;
}
if (response.body() != null) {
log.error("{}, 请求{}失败, {}", func_, url, response.body().string());
} else {
log.error("{}, 请求{}失败", func_, url);
}
return null;
} catch (Exception e) {
log.error("{}, 请求{}失败", func_, url, e);
return null;
}
}
private SecureComponent getSecureComponent() {
if (Objects.isNull(secureComponent)) {
secureComponent = new SecureSM2Component();
}
return secureComponent;
}
/**
* 解密用户真名
*
* @return 真名
*/
public String decryptRealName(String name) throws DecryptFailureException {
return securityContext.decrypt(name, privateKey);
}
}