2026-01-09 09:43:05 +08:00

359 lines
14 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.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.ex.EbikeException;
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.EbikeRealNameSignDto;
import com.cdzy.user.model.dto.EbikeRealNameVerifyDto;
import com.cdzy.user.model.dto.UserValidateDto;
import com.cdzy.user.model.entity.EbikeUserRealInfo;
import com.cdzy.user.model.vo.EbikeUserVo;
import com.cdzy.user.service.EbikeUserRealInfoService;
import com.cdzy.user.service.EbikeUserService;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import okhttp3.*;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.Security;
import java.time.LocalDateTime;
import java.util.*;
/**
* 验证工具类
*
* @author yanglei
* @since 2025-10-15 09:30
*/
@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;
@Resource
private ObjectMapper objectMapper;
//暂时硬编码后续放到redis或者config中【本地数据加密解密】
private SecureComponent secureComponent;
private static final String PRIVATE_KEY = "f8209a2ebe6691e41e1f2b667bfe71f0b511716cc0f7c4452502fc12ec3957e4";
private static final String PUBLIC_KEY = "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 JsonNode 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 验证用户实名请求
*/
@Transactional
public void verifyRealName(UserValidateDto userValidateDto) {
EbikeUserVo userInfo = ebikeUserService.getUserInfoByUserId(userValidateDto.getUserId());
if (userInfo == null) {
throw new EbikeException("用户不存在");
}
Integer realNameStatus = userInfo.getUserRealNameStatus();
if (realNameStatus != null && realNameStatus.equals(RealNameAttestationStatus.CERTIFIED)) {
throw new EbikeException("用户已实名验证");
}
try {
// 1. 加密数据
String encryptedName = securityContext.encrypt(userValidateDto.getName(), realNameVerifyConfig.getServerPublicKey());
String encryptedIdCard = securityContext.encrypt(userValidateDto.getIdCard(), realNameVerifyConfig.getServerPublicKey());
String encryptedTimestamp = securityContext.encrypt(String.valueOf(System.currentTimeMillis()), realNameVerifyConfig.getServerPublicKey());
EbikeRealNameSignDto signData = new EbikeRealNameSignDto(encryptedName, encryptedIdCard, encryptedTimestamp);
String sign = securityContext.sign(signData.toMap(), realNameVerifyConfig.getClientPrivateKey());
EbikeRealNameVerifyDto ebikeRealNameVerifyDto = new EbikeRealNameVerifyDto();
ebikeRealNameVerifyDto.setName(encryptedName);
ebikeRealNameVerifyDto.setIdCard(encryptedIdCard);
ebikeRealNameVerifyDto.setTimestamp(encryptedTimestamp);
ebikeRealNameVerifyDto.setSign(sign);
ebikeRealNameVerifyDto.setKey(realNameVerifyConfig.getApiKey());
// 2. 调用第三方实名验证
JsonNode result = httpPost("验证用户实名", REALNAME_VERIFY_URL, ebikeRealNameVerifyDto, null);
log.info("验证用户实名结果: {}", result);
if (result == null) {
throw new EbikeException("验证用户实名失败");
}
String code = result.has("code") ? result.get("code").asText() : "";
if (!"10000".equals(code)) {
String message = result.has("message") ? result.get("message").asText() : "未知错误";
log.error("验证用户实名失败, code: {}, message: {}", code, message);
throw new EbikeException("验证用户实名失败");
}
// 3. 解密并解析结果
String data = securityContext.decrypt(result.get("data").asText(), realNameVerifyConfig.getClientPrivateKey());
JsonNode jsonData = objectMapper.readTree(data);
if (jsonData.has("result") && "1".equals(jsonData.get("result").asText())) {
// 验证成功直接返回
updateRealNameInfo(userValidateDto, true);
log.info("验证用户实名成功");
return;
} else {
// 验证失败
String failReason = jsonData.has("msg") ? jsonData.get("msg").asText() : "实名验证未通过";
log.warn("实名验证未通过,原因: {}", failReason);
}
// 4. 验证失败:更新为未认证
updateRealNameInfo(userValidateDto, false);
throw new EbikeException("验证用户实名失败");
} catch (EncryptFailureException e) {
throw new EbikeException("加密失败");
} catch (SignFailureException e) {
throw new EbikeException("签名失败");
} catch (DecryptFailureException e) {
throw new EbikeException("结果解密失败");
} catch (JsonProcessingException e) {
throw new EbikeException("数据解析失败");
}
}
/**
* 更新用户实名信息
*
* @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, PUBLIC_KEY));
ebikeUserRealinfo.setUserIdCard(getSecureComponent().encrypt(idCard, PUBLIC_KEY));
ebikeUserRealinfo.setAttestationStatus(isValid ? RealNameAttestationStatus.CERTIFIED : RealNameAttestationStatus.PENDING_REVIEW);
ebikeUserRealinfo.setAuditTime(LocalDateTime.now());
ebikeUserRealInfoService.save(ebikeUserRealinfo);
} else {
ebikeUserRealinfo.setUserRealName(getSecureComponent().encrypt(realName, PUBLIC_KEY));
ebikeUserRealinfo.setUserIdCard(getSecureComponent().encrypt(idCard, PUBLIC_KEY));
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 JsonNode 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();
return executeAndParseResponse(func_, url, request);
}
/**
* 发送HTTP POST参数请求。
*
* @param func_ 功能描述
* @param url 请求URL
* @param dto 请求参数
* @return 响应结果
*/
private JsonNode httpPost(String func_, String url, EbikeRealNameVerifyDto dto, Map<String, Object> body) {
StringBuilder queryString = new StringBuilder();
appendParam(queryString, "name", dto.getName());
appendParam(queryString, "idcard", dto.getIdCard());
appendParam(queryString, "timestamp", dto.getTimestamp());
appendParam(queryString, "sign", dto.getSign());
appendParam(queryString, "key", dto.getKey());
String requestUrl = url;
if (!queryString.isEmpty()) {
requestUrl += "?" + queryString.toString();
}
Request.Builder builder = new Request.Builder().url(requestUrl);
if (body != null) {
MediaType typeJson = MediaType.parse("application/json; charset=utf-8");
try {
String jsonBody = objectMapper.writeValueAsString(body);
RequestBody requestBody = RequestBody.create(jsonBody, typeJson);
builder.post(requestBody);
} catch (JsonProcessingException e) {
log.error("{}, 请求体序列化失败", func_, e);
return null;
}
} else {
builder.post(RequestBody.create(new byte[0]));
}
Request request = builder.build();
return executeAndParseResponse(func_, url, request);
}
private JsonNode executeAndParseResponse(String func_, String url, Request request) {
try (Response response = client.newCall(request).execute()) {
if (response.isSuccessful()) {
if (response.body() != null) {
String responseBody = response.body().string();
return objectMapper.readTree(responseBody);
}
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 void appendParam(StringBuilder sb, String key, String value) {
if (value == null) {
return;
}
if (!sb.isEmpty()) {
sb.append("&");
}
sb.append(key).append("=").append(value);
}
private SecureComponent getSecureComponent() {
if (Objects.isNull(secureComponent)) {
secureComponent = new SecureSM2Component();
}
return secureComponent;
}
/**
* 解密用户真名
*
* @return 真名
*/
public String decryptRealName(String name) throws DecryptFailureException {
return securityContext.decrypt(name, PRIVATE_KEY);
}
}