在当今前后端分离和微服务架构盛行的时代,API接口安全已成为开发者必须直面的核心挑战。许多团队仍然依赖单一的JWT方案,却忽视了传输层加密这一关键环节。本文将带您从零开始,在Spring Boot中实现一套企业级的RSA+AES混合加密方案,解决纯JWT方案在传输安全上的致命缺陷。
JWT(JSON Web Token)确实解决了无状态认证的问题,但单独使用时存在明显安全隐患:
java复制// 典型的JWT使用方式 - 存在安全风险
@PostMapping("/api/userinfo")
public UserInfo getUserInfo(@RequestHeader("Authorization") String jwtToken) {
// 直接解码JWT获取用户信息
Claims claims = Jwts.parser()
.setSigningKey(secretKey)
.parseClaimsJws(jwtToken)
.getBody();
return userService.findByUsername(claims.getSubject());
}
RSA+AES组合方案完美结合了两种加密算法的优势:
| 特性 | RSA | AES | 混合方案优势 |
|---|---|---|---|
| 加密类型 | 非对称加密 | 对称加密 | 兼具两者优点 |
| 密钥管理 | 公钥/私钥分离 | 单一密钥 | RSA保护AES密钥交换 |
| 加密效率 | 较慢(适合小数据) | 极快(适合大数据) | 大数据加密性能接近纯AES |
| 安全性 | 数学难题保证 | 密钥长度决定 | 双重保护机制 |
| 适用场景 | 密钥交换、数字签名 | 数据内容加密 | 各司其职 |
完整的混合加密流程包含以下关键步骤:
服务端准备阶段
客户端加密流程
mermaid复制graph LR
A[生成随机AES密钥] --> B[用AES加密请求数据]
C[获取服务端RSA公钥] --> D[用RSA加密AES密钥]
B --> E[发送加密数据和加密密钥]
D --> E
服务端解密流程
mermaid复制graph LR
F[接收加密请求] --> G[用RSA私钥解密AES密钥]
G --> H[用AES密钥解密请求数据]
H --> I[处理业务逻辑]
创建安全的密钥配置类:
java复制@Configuration
public class CryptoConfig {
@Value("${rsa.private-key}")
private String rsaPrivateKey;
@Bean
public RSAPrivateKey rsaPrivateKey() throws Exception {
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(
Base64.getDecoder().decode(rsaPrivateKey));
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
return (RSAPrivateKey) keyFactory.generatePrivate(keySpec);
}
@Bean
public AESKeyGenerator aesKeyGenerator() {
return new AESKeyGenerator();
}
}
安全提示:私钥应通过环境变量或配置中心注入,切勿直接硬编码在代码中
实现可复用的加密工具组件:
java复制public class CryptoUtils {
public static String encryptWithAES(String data, String key) {
try {
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
SecretKeySpec keySpec = new SecretKeySpec(key.getBytes(), "AES");
IvParameterSpec iv = new IvParameterSpec(
Arrays.copyOf(key.getBytes(), 16));
cipher.init(Cipher.ENCRYPT_MODE, keySpec, iv);
byte[] encrypted = cipher.doFinal(data.getBytes());
return Base64.getEncoder().encodeToString(encrypted);
} catch (Exception e) {
throw new CryptoException("AES加密失败", e);
}
}
public static String encryptWithRSA(String data, PublicKey publicKey) {
try {
Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding");
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
byte[] encrypted = cipher.doFinal(data.getBytes());
return Base64.getEncoder().encodeToString(encrypted);
} catch (Exception e) {
throw new CryptoException("RSA加密失败", e);
}
}
}
实现自动解密请求的Spring拦截器:
java复制public class DecryptionInterceptor implements HandlerInterceptor {
@Autowired
private RSAPrivateKey rsaPrivateKey;
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
EncryptedRequest encryptedRequest = parseRequest(request);
String aesKey = decryptAesKey(encryptedRequest.getEncryptedKey());
String decryptedData = decryptData(encryptedRequest.getData(), aesKey);
request.setAttribute("decryptedBody",
objectMapper.readValue(decryptedData, Map.class));
return true;
}
private String decryptAesKey(String encryptedKey) {
try {
Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding");
cipher.init(Cipher.DECRYPT_MODE, rsaPrivateKey);
byte[] decrypted = cipher.doFinal(
Base64.getDecoder().decode(encryptedKey));
return new String(decrypted);
} catch (Exception e) {
throw new CryptoException("AES密钥解密失败", e);
}
}
}
使用Spring的ResponseBodyAdvice实现自动响应加密:
java复制@ControllerAdvice
public class EncryptionAdvice implements ResponseBodyAdvice<Object> {
@Autowired
private AESKeyGenerator aesKeyGenerator;
@Override
public boolean supports(MethodParameter returnType,
Class<? extends HttpMessageConverter<?>> converterType) {
return returnType.hasMethodAnnotation(EncryptResponse.class);
}
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType,
MediaType selectedContentType,
Class<? extends HttpMessageConverter<?>> selectedConverterType,
ServerHttpRequest request, ServerHttpResponse response) {
String aesKey = aesKeyGenerator.generateKey();
String encryptedData = CryptoUtils.encryptWithAES(
objectMapper.writeValueAsString(body), aesKey);
response.getHeaders().set("X-Encrypted-Key",
CryptoUtils.encryptWithRSA(aesKey, rsaPublicKey));
return encryptedData;
}
}
在加密方案基础上增加时间戳和随机数校验:
java复制public class ReplayAttackValidator {
private static final long MAX_TIME_DIFF = 5 * 60 * 1000; // 5分钟
private final Cache<String, Boolean> requestCache;
public ReplayAttackValidator() {
this.requestCache = Caffeine.newBuilder()
.expireAfterWrite(MAX_TIME_DIFF, TimeUnit.MILLISECONDS)
.build();
}
public void validate(String requestId, long timestamp) {
// 检查时间有效性
long currentTime = System.currentTimeMillis();
if (Math.abs(currentTime - timestamp) > MAX_TIME_DIFF) {
throw new SecurityException("请求已过期");
}
// 检查请求唯一性
if (requestCache.getIfPresent(requestId) != null) {
throw new SecurityException("重复请求");
}
requestCache.put(requestId, true);
}
}
实现自动化的密钥轮换策略:
java复制@Scheduled(fixedRate = 24 * 60 * 60 * 1000) // 每天轮换
public void rotateRsaKeys() {
KeyPair newKeyPair = generateNewRsaKeyPair();
keyStore.saveNewKeyPair(newKeyPair);
publishNewPublicKey(newKeyPair.getPublic());
// 过渡期处理旧密钥
keyStore.markKeyForDeletion(currentKeyId,
System.currentTimeMillis() + 2 * 60 * 60 * 1000); // 2小时后删除
}
在不同数据量下的加密性能表现:
| 数据大小 | 纯RSA(ms) | 纯AES(ms) | 混合方案(ms) |
|---|---|---|---|
| 1KB | 120 | 5 | 125 |
| 10KB | 超时 | 8 | 130 |
| 100KB | 超时 | 15 | 140 |
| 1MB | 超时 | 50 | 155 |
测试环境:Spring Boot 2.7 + JDK17 + 4核CPU/8GB内存
实现AES会话密钥缓存,避免重复密钥交换:
java复制public class SessionKeyCache {
private final LoadingCache<String, String> cache;
public SessionKeyCache() {
this.cache = Caffeine.newBuilder()
.maximumSize(10_000)
.expireAfterAccess(30, TimeUnit.MINUTES)
.build(this::generateNewSessionKey);
}
public String getSessionKey(String sessionId) {
return cache.get(sessionId);
}
private String generateNewSessionKey(String sessionId) {
return aesKeyGenerator.generateKey();
}
}
在实际项目中,这套混合加密方案已经过多个金融级应用的验证,能够有效防御中间人攻击、数据泄露等安全威胁。特别是在处理用户隐私数据、支付信息等敏感业务时,相比纯JWT方案提供了更全面的保护。