feat(system): 加密方式调整

This commit is contained in:
2026-03-30 16:22:20 +08:00
parent 1e47618406
commit a22991f7a0
14 changed files with 100 additions and 28 deletions

View File

@@ -63,8 +63,9 @@ public class ApiEncryptProperties {
* 注意:
* 1. 如果是【对称加密】时,它「后端」对应的是“密钥”。对应的,「前端」也对应的也是“密钥”。
* 2. 如果是【非对称加密】时,它「后端」对应的是“公钥”。对应的,「前端」对应的是“私钥”。(重要!!!)
*
* 当前 system 模块密码加密方案不启用响应加密,因此该配置可为空。
*/
@NotEmpty(message = "响应的加密密钥不能为空")
private String responseKey;
}

View File

@@ -4,6 +4,7 @@ import com.njcn.rdms.framework.common.enums.WebFilterOrderEnum;
import com.njcn.rdms.framework.encrypt.core.filter.ApiEncryptFilter;
import com.njcn.rdms.framework.web.config.WebProperties;
import com.njcn.rdms.framework.web.core.handler.GlobalExceptionHandler;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
@@ -24,9 +25,10 @@ public class RdmsApiEncryptAutoConfiguration {
public FilterRegistrationBean<ApiEncryptFilter> apiEncryptFilter(WebProperties webProperties,
ApiEncryptProperties apiEncryptProperties,
RequestMappingHandlerMapping requestMappingHandlerMapping,
GlobalExceptionHandler globalExceptionHandler) {
GlobalExceptionHandler globalExceptionHandler,
ObjectMapper objectMapper) {
ApiEncryptFilter filter = new ApiEncryptFilter(webProperties, apiEncryptProperties,
requestMappingHandlerMapping, globalExceptionHandler);
requestMappingHandlerMapping, globalExceptionHandler, objectMapper);
return createFilterBean(filter, WebFilterOrderEnum.API_ENCRYPT_FILTER);
}

View File

@@ -15,6 +15,13 @@ public @interface ApiEncrypt {
*/
boolean request() default true;
/**
* 需要解密的请求字段
*
* 仅在 request = true 时生效
*/
String[] requestFields() default {};
/**
* 是否对响应结果进行加密,默认 true
*/

View File

@@ -1,7 +1,11 @@
package com.njcn.rdms.framework.encrypt.core.filter;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.StrUtil;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import cn.hutool.crypto.asymmetric.AsymmetricDecryptor;
import cn.hutool.crypto.asymmetric.KeyType;
import cn.hutool.crypto.symmetric.SymmetricDecryptor;
@@ -14,6 +18,9 @@ import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.List;
import static com.njcn.rdms.framework.common.exception.util.ServiceExceptionUtil.invalidParamException;
/**
* 解密请求 {@link HttpServletRequestWrapper} 实现类
@@ -25,16 +32,41 @@ public class ApiDecryptRequestWrapper extends HttpServletRequestWrapper {
private final byte[] body;
public ApiDecryptRequestWrapper(HttpServletRequest request,
ObjectMapper objectMapper,
List<String> requestFields,
SymmetricDecryptor symmetricDecryptor,
AsymmetricDecryptor asymmetricDecryptor) throws IOException {
super(request);
// 读取 body允许 HEX、BASE64 传输
String requestBody = StrUtil.utf8Str(
IoUtil.readBytes(request.getInputStream(), false));
if (CollUtil.isEmpty(requestFields)) {
throw invalidParamException("请求解密失败,请刷新页面后重试");
}
String requestBody = StrUtil.utf8Str(IoUtil.readBytes(request.getInputStream(), false));
if (StrUtil.isBlank(requestBody)) {
throw invalidParamException("请求解密失败,请刷新页面后重试");
}
// 解密 body
body = symmetricDecryptor != null ? symmetricDecryptor.decrypt(requestBody)
: asymmetricDecryptor.decrypt(requestBody, KeyType.PrivateKey);
JsonNode requestJson;
try {
requestJson = objectMapper.readTree(requestBody);
} catch (Exception ex) {
throw invalidParamException("请求解密失败,请刷新页面后重试");
}
if (!(requestJson instanceof ObjectNode requestObject)) {
throw invalidParamException("请求解密失败,请刷新页面后重试");
}
for (String requestField : requestFields) {
JsonNode fieldNode = requestObject.get(requestField);
if (fieldNode == null || fieldNode.isNull() || !fieldNode.isTextual() || StrUtil.isBlank(fieldNode.asText())) {
throw invalidParamException("请求解密失败,请刷新页面后重试");
}
byte[] decryptedBytes = symmetricDecryptor != null
? symmetricDecryptor.decrypt(fieldNode.asText())
: asymmetricDecryptor.decrypt(fieldNode.asText(), KeyType.PrivateKey);
requestObject.put(requestField, StrUtil.utf8Str(decryptedBytes));
}
body = objectMapper.writeValueAsBytes(requestObject);
}
@Override

View File

@@ -6,6 +6,7 @@ import cn.hutool.crypto.asymmetric.AsymmetricDecryptor;
import cn.hutool.crypto.asymmetric.AsymmetricEncryptor;
import cn.hutool.crypto.symmetric.SymmetricDecryptor;
import cn.hutool.crypto.symmetric.SymmetricEncryptor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.njcn.rdms.framework.common.pojo.CommonResult;
import com.njcn.rdms.framework.common.util.object.ObjectUtils;
import com.njcn.rdms.framework.common.util.servlet.ServletUtils;
@@ -26,6 +27,7 @@ import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandl
import org.springframework.web.util.ServletRequestPathUtils;
import java.io.IOException;
import java.util.Arrays;
import static com.njcn.rdms.framework.common.exception.util.ServiceExceptionUtil.invalidParamException;
@@ -43,12 +45,17 @@ import static com.njcn.rdms.framework.common.exception.util.ServiceExceptionUtil
@Slf4j
public class ApiEncryptFilter extends ApiRequestFilter {
private static final String MISSING_ENCRYPT_HEADER_MESSAGE = "当前接口要求加密传输,请刷新页面后重试";
private static final String DECRYPT_FAILED_MESSAGE = "请求解密失败,请刷新页面后重试";
private final ApiEncryptProperties apiEncryptProperties;
private final RequestMappingHandlerMapping requestMappingHandlerMapping;
private final GlobalExceptionHandler globalExceptionHandler;
private final ObjectMapper objectMapper;
private final SymmetricDecryptor requestSymmetricDecryptor;
private final AsymmetricDecryptor requestAsymmetricDecryptor;
@@ -58,21 +65,25 @@ public class ApiEncryptFilter extends ApiRequestFilter {
public ApiEncryptFilter(WebProperties webProperties,
ApiEncryptProperties apiEncryptProperties,
RequestMappingHandlerMapping requestMappingHandlerMapping,
GlobalExceptionHandler globalExceptionHandler) {
GlobalExceptionHandler globalExceptionHandler,
ObjectMapper objectMapper) {
super(webProperties);
this.apiEncryptProperties = apiEncryptProperties;
this.requestMappingHandlerMapping = requestMappingHandlerMapping;
this.globalExceptionHandler = globalExceptionHandler;
this.objectMapper = objectMapper;
if (StrUtil.equalsIgnoreCase(apiEncryptProperties.getAlgorithm(), "AES")) {
this.requestSymmetricDecryptor = SecureUtil.aes(StrUtil.utf8Bytes(apiEncryptProperties.getRequestKey()));
this.requestAsymmetricDecryptor = null;
this.responseSymmetricEncryptor = SecureUtil.aes(StrUtil.utf8Bytes(apiEncryptProperties.getResponseKey()));
this.responseSymmetricEncryptor = StrUtil.isNotBlank(apiEncryptProperties.getResponseKey())
? SecureUtil.aes(StrUtil.utf8Bytes(apiEncryptProperties.getResponseKey())) : null;
this.responseAsymmetricEncryptor = null;
} else if (StrUtil.equalsIgnoreCase(apiEncryptProperties.getAlgorithm(), "RSA")) {
this.requestSymmetricDecryptor = null;
this.requestAsymmetricDecryptor = SecureUtil.rsa(apiEncryptProperties.getRequestKey(), null);
this.responseSymmetricEncryptor = null;
this.responseAsymmetricEncryptor = SecureUtil.rsa(null, apiEncryptProperties.getResponseKey());
this.responseAsymmetricEncryptor = StrUtil.isNotBlank(apiEncryptProperties.getResponseKey())
? SecureUtil.rsa(null, apiEncryptProperties.getResponseKey()) : null;
} else {
// 补充说明:如果要支持 SM2、SM4 等算法,可在此处增加对应实例的创建,并添加相应的 Maven 依赖即可。
throw new IllegalArgumentException("不支持的加密算法:" + apiEncryptProperties.getAlgorithm());
@@ -86,9 +97,10 @@ public class ApiEncryptFilter extends ApiRequestFilter {
// 获取 @ApiEncrypt 注解
ApiEncrypt apiEncrypt = getApiEncrypt(request);
boolean requestEnable = apiEncrypt != null && apiEncrypt.request();
boolean responseEnable = apiEncrypt != null && apiEncrypt.response();
boolean responseEnable = apiEncrypt != null && apiEncrypt.response()
&& (responseSymmetricEncryptor != null || responseAsymmetricEncryptor != null);
String encryptHeader = request.getHeader(apiEncryptProperties.getHeader());
if (!requestEnable && !responseEnable && StrUtil.isBlank(encryptHeader)) {
if (!requestEnable && !responseEnable) {
chain.doFilter(request, response);
return;
}
@@ -97,13 +109,19 @@ public class ApiEncryptFilter extends ApiRequestFilter {
if (ObjectUtils.equalsAny(HttpMethod.valueOf(request.getMethod()),
HttpMethod.POST, HttpMethod.PUT, HttpMethod.DELETE)) {
try {
if (StrUtil.isNotBlank(encryptHeader)) {
request = new ApiDecryptRequestWrapper(request,
if (requestEnable) {
if (StrUtil.isBlank(encryptHeader)) {
throw invalidParamException(MISSING_ENCRYPT_HEADER_MESSAGE);
}
request = new ApiDecryptRequestWrapper(request, objectMapper,
Arrays.asList(apiEncrypt.requestFields()),
requestSymmetricDecryptor, requestAsymmetricDecryptor);
} else if (requestEnable) {
throw invalidParamException("请求未包含加密标头,请检查是否正确配置了加密标头");
}
} catch (Exception ex) {
if (!(ex instanceof com.njcn.rdms.framework.common.exception.ServiceException)
|| !StrUtil.equals(ex.getMessage(), MISSING_ENCRYPT_HEADER_MESSAGE)) {
ex = invalidParamException(DECRYPT_FAILED_MESSAGE);
}
CommonResult<?> result = globalExceptionHandler.allExceptionHandler(request, ex);
ServletUtils.writeJSON(response, result);
return;