2025-11-11 10:08:52 +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.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.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 验证用户实名请求
* @return 验证结果
*/
@Transactional
public JsonResult<?> verifyRealName(UserValidateDto userValidateDto) {
EbikeUserVo userInfo = ebikeUserService.getUserInfoByUserId(userValidateDto.getUserId());
if (userInfo == null) {
log.error("用户{}不存在", userValidateDto.getUserId());
return JsonResult.failed("用户不存在");
}
// 校验是否已进行验证
Integer realNameStatus = userInfo.getUserRealNameStatus();
if (realNameStatus != null && realNameStatus.equals(RealNameAttestationStatus.CERTIFIED)) {
return JsonResult.failed("用户已实名验证");
}
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) {
log.error("验证用户实名失败");
return JsonResult.failed("验证用户实名失败");
}
if (result.has("code") && "10000".equals(result.get("code").asText())) {
// 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())) {
// 4. 更新用户实名信息
updateRealNameInfo(userValidateDto, true);
return JsonResult.success("验证用户实名成功");
}
}
updateRealNameInfo(userValidateDto, false);
String errorCode = result.has("code") ? result.get("code").asText() : "未知错误";
String errorMessage = result.has("message") ? result.get("message").asText() : "验证失败";
log.error("验证用户实名失败,{} {}", errorCode, errorMessage);
return JsonResult.failed(errorCode + ", " + errorMessage);
} 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("结果解密失败");
} catch (JsonProcessingException e) {
log.error("JSON解析失败", 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, 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);
}
}