提供给C端使用的程序,特别是银行、电商类的项目,其实都挺害怕参数被抓包,进而进行接口攻击或者推测出表结构、数据结构等。所以一个全局化的参数加密解密功能还是很有必要的。
一、整体架构设计
本方案采用RSA+AES混合加密体系,结合Spring的RequestBodyAdvice和ResponseBodyAdvice机制,实现请求/响应全流程的透明化加解密:
- 客户端生成:每次请求随机生成AES密钥和IV(初始化向量)
- RSA加密:用服务端公钥加密AES密钥和IV
- 请求传输:密文参数 + 加密后的AES密钥(HTTP Header)
- 服务端解密:用RSA私钥解密获得AES密钥 → 解密请求体
- 动态开关:通过Apollo配置中心控制全局加解密开关
核心处理流程

二、核心实现
1. 定义配置类
@Configuration
public class EncryptionConfig {
@Bean
@ConfigurationProperties(prefix = "encryption")
public EncryptionProperties encryptionProperties() {
return new EncryptionProperties();
}
}
@Data
public class EncryptionProperties {
private boolean enabled;
private List<String> excludedPaths = new ArrayList<>();
}
2. 实现注解控制
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SkipDecrypt {
// true表示跳过解密,false表示需要解密
boolean value() default true;
}
3. 加解密工具类
public class CryptoUtils {
// RSA解密(获取AES密钥)
public static byte[] rsaDecrypt(byte[] encryptedData, PrivateKey privateKey) {
try {
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher.init(Cipher.DECRYPT_MODE, privateKey);
return cipher.doFinal(encryptedData);
} catch (Exception e) {
throw new CryptoException("RSA解密失败", e);
}
}
// AES解密请求体
public static String aesDecrypt(String encryptedData, byte[] aesKey, byte[] iv) {
try {
SecretKeySpec keySpec = new SecretKeySpec(aesKey, "AES");
IvParameterSpec ivSpec = new IvParameterSpec(iv);
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
byte[] decoded = Base64.getDecoder().decode(encryptedData);
return new String(cipher.doFinal(decoded), StandardCharsets.UTF_8);
} catch (Exception e) {
throw new CryptoException("AES解密失败", e);
}
}
}
4. 实现RequestBodyAdvice解密控制
@ControllerAdvice
public class DecryptRequestBodyAdvice implements RequestBodyAdvice {
@Autowired
private EncryptionProperties properties;
@Autowired
private PrivateKey rsaPrivateKey; // 注入预加载的RSA私钥
@Override
public boolean supports(MethodParameter methodParameter, Type targetType,
Class<? extends HttpMessageConverter<?>> converterType) {
// 1. 检查Apollo全局开关
if (!properties.isEnabled()) return false;
HttpServletRequest request =
((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
String path = request.getRequestURI();
// 2. 检查排除路径
if (properties.getExcludedPaths().contains(path)) {
return false;
}
// 3. 检查方法注解(默认需要解密,只有显式设置false才跳过)
SkipDecrypt annotation = methodParameter.getMethodAnnotation(SkipDecrypt.class);
return annotation == null || !annotation.value();
}
@Override
public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter,
Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
HttpServletRequest request =
((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
// 获取Header中的加密密钥
String encryptedAesKey = request.getHeader("encrypted-aes-key");
String encryptedIv = request.getHeader("encrypted-iv");
// RSA解密获得AES密钥和IV
byte[] aesKey = CryptoUtils.rsaDecrypt(Base64.getDecoder().decode(encryptedAesKey), rsaPrivateKey);
byte[] iv = CryptoUtils.rsaDecrypt(Base64.getDecoder().decode(encryptedIv), rsaPrivateKey);
try {
// 读取并解密请求体
String encryptedBody = StreamUtils.copyToString(inputMessage.getBody(), StandardCharsets.UTF_8);
String decryptedBody = CryptoUtils.aesDecrypt(encryptedBody, aesKey, iv);
// 构建解密后的输入流
return new DecryptedHttpInputMessage(
new ByteArrayInputStream(decryptedBody.getBytes()),
inputMessage.getHeaders()
);
} catch (IOException e) {
throw new IllegalStateException("请求体读取失败", e);
}
}
// 其他必要方法实现...
@Override
public Object afterBodyRead(Object body, HttpInputMessage inputMessage, ...) { return body; }
@Override
public Object handleEmptyBody(Object body, ...) { return body; }
}
三、动态开关与路径排除策略
1. Apollo监听配置变更
@Component
public class EncryptionConfigRefresher {
@Autowired
private EncryptionProperties properties;
@ApolloConfigChangeListener
public void onChange(ConfigChangeEvent changeEvent) {
if (changeEvent.isChanged("encryption.enabled")) {
boolean newValue = Boolean.parseBoolean(
changeEvent.getChange("encryption.enabled").getNewValue());
properties.setEnabled(newValue);
}
if (changeEvent.isChanged("encryption.excludedPaths")) {
String paths = changeEvent.getChange("encryption.excludedPaths").getNewValue();
properties.setExcludedPaths(Arrays.asList(paths.split(",")));
}
}
}
2. 路径匹配逻辑优化
// 在supports方法中实现智能路径匹配
private boolean isExcludedPath(String requestPath) {
return properties.getExcludedPaths().stream()
.anyMatch(excludedPath -> {
// 支持Ant风格路径匹配
PathMatcher matcher = new AntPathMatcher();
return matcher.match(excludedPath, requestPath);
});
}
四、总结
本文实现的全局加解密方案具有以下优势:
- 零侵入性:业务代码无需感知加解密过程
- 动态控制:通过Apollo实现运行时配置热更新
- 灵活策略:支持全局开关、路径排除和方法级注解控制
- 安全传输:采用RSA+AES混合加密体系,确保密钥安全交换
通过RequestBodyAdvice/ResponseBodyAdvice机制,我们成功构建了可插拔的加解密架构。该方案已在多个金融级项目中验证,可支撑日均百万级API调用的安全需求。



848

被折叠的 条评论
为什么被折叠?



