SpringBoot 实现 RAS+AES 自动接口解密
SpringBoot 实现 RSA + AES 自动接口解密方案
在实际项目中,为了保证接口数据传输的安全性,常常需要采用混合加密方案。下面我将详细介绍如何在SpringBoot中实现RSA非对称加密传输AES密钥,然后使用AES对称加密解密请求体的完整方案。
一、方案设计
1. 加密流程
客户端生成随机AES密钥
使用RSA公钥加密AES密钥
使用AES密钥加密请求数据
将加密后的AES密钥和加密数据一起传输
2. 解密流程
服务端使用RSA私钥解密获取AES密钥
使用AES密钥解密请求体
处理业务逻辑
响应时同样使用AES加密返回数据
二、核心实现
1. 添加依赖
xml
<!-- 加密相关 -->
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcpkix-jdk15on</artifactId>
<version>1.68</version>
</dependency>
2. 加密工具类
public class CryptoUtils {
// RSA密钥对生成
public static KeyPair generateRSAKeyPair() throws NoSuchAlgorithmException {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
keyPairGenerator.initialize(2048);
return keyPairGenerator.generateKeyPair();
}
// RSA加密
public static byte[] rsaEncrypt(byte[] data, PublicKey publicKey) throws Exception {
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
return cipher.doFinal(data);
}
// RSA解密
public static byte[] rsaDecrypt(byte[] data, PrivateKey privateKey) throws Exception {
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher.init(Cipher.DECRYPT_MODE, privateKey);
return cipher.doFinal(data);
}
// AES加密
public static String aesEncrypt(String data, String key) throws Exception {
SecretKeySpec secretKey = new SecretKeySpec(key.getBytes(), "AES");
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
return Base64.getEncoder().encodeToString(cipher.doFinal(data.getBytes(StandardCharsets.UTF_8)));
}
// AES解密
public static String aesDecrypt(String encryptedData, String key) throws Exception {
SecretKeySpec secretKey = new SecretKeySpec(key.getBytes(), "AES");
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, secretKey);
byte[] original = cipher.doFinal(Base64.getDecoder().decode(encryptedData));
return new String(original, StandardCharsets.UTF_8);
}
}
3. 自动解密注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DecryptRequest {
boolean value() default true;
}
4. 请求解密拦截器
@ControllerAdvice
public class DecryptRequestBodyAdvice implements RequestBodyAdvice {
@Value("${rsa.private-key}")
private String privateKey;
@Override
public boolean supports(MethodParameter methodParameter, Type targetType,
Class<? extends HttpMessageConverter<?>> converterType) {
return methodParameter.hasMethodAnnotation(DecryptRequest.class);
}
@Override
public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter,
Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
try {
// 1. 获取加密的AES密钥和请求体
String encryptedAesKey = inputMessage.getHeaders().getFirst("X-Encrypt-Aes-Key");
String encryptedBody = StreamUtils.copyToString(inputMessage.getBody(), StandardCharsets.UTF_8);
// 2. RSA解密AES密钥
byte[] aesKeyBytes = CryptoUtils.rsaDecrypt(
Base64.getDecoder().decode(encryptedAesKey),
getPrivateKey(privateKey)
);
String aesKey = new String(aesKeyBytes, StandardCharsets.UTF_8);
// 3. AES解密请求体
String decryptedBody = CryptoUtils.aesDecrypt(encryptedBody, aesKey);
// 4. 返回解密后的输入流
return new ByteArrayHttpMessageConverter().read(
String.class,
new ByteArrayInputStream(decryptedBody.getBytes())
);
} catch (Exception e) {
throw new RuntimeException("解密失败", e);
}
}
// 其他必要方法实现...
private PrivateKey getPrivateKey(String privateKeyStr) throws Exception {
byte[] keyBytes = Base64.getDecoder().decode(privateKeyStr);
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
return keyFactory.generatePrivate(keySpec);
}
}
5. 响应加密拦截器
@ControllerAdvice
public class EncryptResponseBodyAdvice implements ResponseBodyAdvice<Object> {
@Override
public boolean supports(MethodParameter returnType,
Class<? extends HttpMessageConverter<?>> converterType) {
return returnType.hasMethodAnnotation(DecryptRequest.class);
}
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType,
MediaType selectedContentType,
Class<? extends HttpMessageConverter<?>> selectedConverterType,
ServerHttpRequest request, ServerHttpResponse response) {
try {
// 从请求头获取AES密钥
String encryptedAesKey = request.getHeaders().getFirst("X-Encrypt-Aes-Key");
String aesKey = // 解密AES密钥(同请求解密逻辑)
// 加密响应体
String responseBody = objectMapper.writeValueAsString(body);
return CryptoUtils.aesEncrypt(responseBody, aesKey);
} catch (Exception e) {
throw new RuntimeException("加密失败", e);
}
}
}
6. 控制器使用示例
@RestController
@RequestMapping("/api")
public class SecureController {
@PostMapping("/secure-data")
@DecryptRequest
public ResponseEntity<?> handleSecureData(@RequestBody Map<String, Object> data) {
// 这里获取到的data已经是解密后的数据
return ResponseEntity.ok(Collections.singletonMap("status", "success"));
}
}
三、客户端实现示例
1. 加密请求示例(JavaScript)
javascript
async function sendEncryptedRequest() {
// 1. 生成随机AES密钥
const aesKey = generateAesKey();
// 2. 使用RSA公钥加密AES密钥
const encryptedAesKey = await rsaEncrypt(aesKey, publicKey);
// 3. 使用AES加密请求数据
const requestData = { username: 'admin', password: '123456' };
const encryptedData = aesEncrypt(JSON.stringify(requestData), aesKey);
// 4. 发送请求
const response = await fetch('/api/secure-data', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Encrypt-Aes-Key': encryptedAesKey
},
body: encryptedData
});
// 5. 解密响应
const encryptedResponse = await response.text();
const decryptedResponse = aesDecrypt(encryptedResponse, aesKey);
return JSON.parse(decryptedResponse);
}
四、安全增强措施
密钥管理:
将RSA私钥存储在安全的地方(如Vault、KMS)
定期轮换密钥
防重放攻击:
添加时间戳和随机数(nonce)
服务端校验请求时效性
完整性校验:
对加密数据添加HMAC签名
服务端验证数据完整性
性能优化:
缓存AES密钥(基于session或请求ID)
使用更高效的加密算法(如AES-GCM)
五、测试与验证
单元测试:
@SpringBootTest
public class CryptoTest {
@Test
public void testRsaAesIntegration() throws Exception {
// 生成RSA密钥对
KeyPair keyPair = CryptoUtils.generateRSAKeyPair();
// 模拟客户端
String aesKey = "this-is-a-secret-key";
String originalData = "{\"name\":\"test\"}";
// 加密流程
byte[] encryptedAesKey = CryptoUtils.rsaEncrypt(aesKey.getBytes(), keyPair.getPublic());
String encryptedData = CryptoUtils.aesEncrypt(originalData, aesKey);
// 模拟服务端解密
byte[] decryptedAesKey = CryptoUtils.rsaDecrypt(encryptedAesKey, keyPair.getPrivate());
String decryptedData = CryptoUtils.aesDecrypt(encryptedData, new String(decryptedAesKey));
assertEquals(originalData, decryptedData);
}
}
集成测试:
使用Postman或curl测试加密接口
验证解密失败时的错误处理
六、部署注意事项
Nginx配置:
nginx
# 确保大文件传输
client_max_body_size 10M;
proxy_read_timeout 300s;
SpringBoot配置:
properties
# 增大最大请求体大小
spring.servlet.multipart.max-request-size=10MB
spring.servlet.multipart.max-file-size=10MB
性能监控:
监控加解密耗时
设置合理的超时时间
这种RSA+AES混合加密方案既保证了密钥传输的安全性,又利用了对称加密的高效性,适合对安全性要求较高的接口场景。
为三方提供加密接口的服务端实现方案
当需要向第三方提供加密接口时,我们需要设计一套完整的加密通信方案。以下是基于SpringBoot的服务端实现代码,采用RSA+AES混合加密方式。
一、服务端加密接口设计方案
1. 加密流程
服务端生成RSA密钥对,将公钥提供给客户端
客户端生成AES密钥,用RSA公钥加密后传给服务端
服务端用RSA私钥解密获取AES密钥
后续通信使用AES加密数据
2. 接口设计
GET /api/encrypt/public-key 获取RSA公钥
POST /api/encrypt/init 初始化会话,交换AES密钥
其他业务接口使用AES加密通信
二、完整服务端实现代码
1. 添加依赖
xml
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcpkix-jdk15on</artifactId>
<version>1.70</version>
</dependency>
2. 加密工具类
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import .nio.charset.StandardCharsets;
import .security.*;
import .security.spec.PKCS8EncodedKeySpec;
import .security.spec.X509EncodedKeySpec;
import .util.Base64;
public class EncryptUtils {
// 生成RSA密钥对
public static KeyPair generateRSAKeyPair() throws NoSuchAlgorithmException {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
keyPairGenerator.initialize(2048);
return keyPairGenerator.generateKeyPair();
}
// RSA公钥加密
public static String rsaEncrypt(String data, PublicKey publicKey) throws Exception {
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
byte[] encryptedBytes = cipher.doFinal(data.getBytes(StandardCharsets.UTF_8));
return Base64.getEncoder().encodeToString(encryptedBytes);
}
// RSA私钥解密
public static String rsaDecrypt(String encryptedData, PrivateKey privateKey) throws Exception {
byte[] data = Base64.getDecoder().decode(encryptedData);
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher.init(Cipher.DECRYPT_MODE, privateKey);
return new String(cipher.doFinal(data), StandardCharsets.UTF_8);
}
// 生成AES密钥
public static String generateAESKey() throws NoSuchAlgorithmException {
KeyGenerator keyGen = KeyGenerator.getInstance("AES");
keyGen.init(256); // AES-256
SecretKey secretKey = keyGen.generateKey();
return Base64.getEncoder().encodeToString(secretKey.getEncoded());
}
// AES加密
public static String aesEncrypt(String data, String base64Key) throws Exception {
byte[] key = Base64.getDecoder().decode(base64Key);
SecretKeySpec secretKey = new SecretKeySpec(key, "AES");
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
byte[] encryptedBytes = cipher.doFinal(data.getBytes(StandardCharsets.UTF_8));
return Base64.getEncoder().encodeToString(encryptedBytes);
}
// AES解密
public static String aesDecrypt(String encryptedData, String base64Key) throws Exception {
byte[] key = Base64.getDecoder().decode(base64Key);
SecretKeySpec secretKey = new SecretKeySpec(key, "AES");
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, secretKey);
byte[] originalBytes = cipher.doFinal(Base64.getDecoder().decode(encryptedData));
return new String(originalBytes, StandardCharsets.UTF_8);
}
// 从字符串加载公钥
public static PublicKey loadPublicKey(String publicKeyStr) throws Exception {
byte[] keyBytes = Base64.getDecoder().decode(publicKeyStr);
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
return keyFactory.generatePublic(keySpec);
}
// 从字符串加载私钥
public static PrivateKey loadPrivateKey(String privateKeyStr) throws Exception {
byte[] keyBytes = Base64.getDecoder().decode(privateKeyStr);
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
return keyFactory.generatePrivate(keySpec);
}
}
3. 会话管理组件
import org.springframework.stereotype.Component;
import .util.Base64;
import .util.Map;
import .util.concurrent.ConcurrentHashMap;
@Component
public class SessionManager {
// 存储会话AES密钥 (实际项目可用Redis替代)
private final Map<String, String> sessionKeys = new ConcurrentHashMap<>();
// 存储RSA密钥对
private final KeyPair rsaKeyPair;
public SessionManager() throws NoSuchAlgorithmException {
this.rsaKeyPair = EncryptUtils.generateRSAKeyPair();
}
// 获取RSA公钥(Base64编码)
public String getPublicKey() {
return Base64.getEncoder().encodeToString(rsaKeyPair.getPublic().getEncoded());
}
// 初始化会话,返回服务端生成的AES密钥
public String initSession(String sessionId, String encryptedClientAesKey) throws Exception {
// 1. 解密客户端AES密钥
String clientAesKey = EncryptUtils.rsaDecrypt(
encryptedClientAesKey,
rsaKeyPair.getPrivate()
);
// 2. 生成服务端AES密钥
String serverAesKey = EncryptUtils.generateAESKey();
// 3. 存储双方协商的密钥 (实际可用组合密钥)
sessionKeys.put(sessionId, serverAesKey);
// 4. 返回服务端AES密钥(用客户端AES密钥加密)
return EncryptUtils.aesEncrypt(serverAesKey, clientAesKey);
}
// 获取会话AES密钥
public String getSessionKey(String sessionId) {
return sessionKeys.get(sessionId);
}
// 移除会话
public void removeSession(String sessionId) {
sessionKeys.remove(sessionId);
}
}
4. 控制器实现
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import .util.Map;
@RestController
@RequestMapping("/api/encrypt")
public class EncryptController {
private final SessionManager sessionManager;
public EncryptController(SessionManager sessionManager) {
this.sessionManager = sessionManager;
}
// 获取RSA公钥
@GetMapping("/public-key")
public ResponseEntity<Map<String, String>> getPublicKey() {
return ResponseEntity.ok(
Map.of("publicKey", sessionManager.getPublicKey())
);
}
// 初始化加密会话
@PostMapping("/init")
public ResponseEntity<Map<String, String>> initSession(
@RequestHeader("X-Session-Id") String sessionId,
@RequestBody Map<String, String> request) throws Exception {
String encryptedServerKey = sessionManager.initSession(
sessionId,
request.get("encryptedAesKey")
);
return ResponseEntity.ok(
Map.of("encryptedServerKey", encryptedServerKey)
);
}
// 示例业务接口 (AES加密)
@PostMapping("/business")
public ResponseEntity<Map<String, Object>> businessApi(
@RequestHeader("X-Session-Id") String sessionId,
@RequestBody Map<String, String> encryptedRequest) throws Exception {
// 1. 获取会话密钥
String aesKey = sessionManager.getSessionKey(sessionId);
if (aesKey == null) {
throw new RuntimeException("会话不存在或已过期");
}
// 2. 解密请求数据
String decryptedData = EncryptUtils.aesDecrypt(
encryptedRequest.get("data"),
aesKey
);
// 3. 处理业务逻辑 (这里只是示例)
System.out.println("解密后的请求数据: " + decryptedData);
// 4. 加密响应数据
String responseData = "{\"status\":\"success\",\"receivedData\":" + decryptedData + "}";
String encryptedResponse = EncryptUtils.aesEncrypt(responseData, aesKey);
return ResponseEntity.ok(
Map.of("data", encryptedResponse)
);
}
}
5. 全局异常处理
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(Exception.class)
public ResponseEntity<Map<String, String>> handleException(Exception e) {
return ResponseEntity.badRequest().body(
Map.of("error", e.getMessage())
);
}
}
6. 配置类
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("*")
.allowedMethods("*")
.allowedHeaders("*");
}
@Bean
public SessionManager sessionManager() throws NoSuchAlgorithmException {
return new SessionManager();
}
}
三、客户端调用示例
1. 客户端调用流程
调用 /api/encrypt/public-key 获取RSA公钥
生成AES密钥,用RSA公钥加密
调用 /api/encrypt/init 初始化会话
后续请求使用协商的AES密钥加密数据
2. Java客户端示例代码
import .util.HashMap;
import .util.Map;
import .util.UUID;
import org.springframework.web.client.RestTemplate;
public class ApiClient {
private final String baseUrl;
private final RestTemplate restTemplate;
private String sessionId;
private String aesKey;
public ApiClient(String baseUrl) {
this.baseUrl = baseUrl;
this.restTemplate = new RestTemplate();
this.sessionId = UUID.randomUUID().toString();
}
public void initSession() throws Exception {
// 1. 获取服务端RSA公钥
Map<?, ?> publicKeyResponse = restTemplate.getForObject(
baseUrl + "/api/encrypt/public-key", Map.class);
String publicKeyStr = (String) publicKeyResponse.get("publicKey");
PublicKey publicKey = EncryptUtils.loadPublicKey(publicKeyStr);
// 2. 生成客户端AES密钥并加密
String clientAesKey = EncryptUtils.generateAESKey();
String encryptedClientAesKey = EncryptUtils.rsaEncrypt(clientAesKey, publicKey);
// 3. 初始化会话
Map<String, String> initRequest = new HashMap<>();
initRequest.put("encryptedAesKey", encryptedClientAesKey);
Map<?, ?> initResponse = restTemplate.postForObject(
baseUrl + "/api/encrypt/init",
initRequest,
Map.class,
sessionId
);
// 4. 解密获取服务端AES密钥
String encryptedServerKey = (String) initResponse.get("encryptedServerKey");
this.aesKey = EncryptUtils.aesDecrypt(encryptedServerKey, clientAesKey);
}
public String callBusinessApi(String requestData) throws Exception {
// 加密请求数据
String encryptedData = EncryptUtils.aesEncrypt(requestData, aesKey);
Map<String, String> request = new HashMap<>();
request.put("data", encryptedData);
// 发送请求
Map<?, ?> response = restTemplate.postForObject(
baseUrl + "/api/encrypt/business",
request,
Map.class,
sessionId
);
// 解密响应数据
return EncryptUtils.aesDecrypt((String) response.get("data"), aesKey);
}
public static void main(String[] args) throws Exception {
ApiClient client = new ApiClient("http://localhost:8080");
client.initSession();
String response = client.callBusinessApi("{\"param1\":\"value1\",\"param2\":123}");
System.out.println("响应数据: " + response);
}
}
四、安全增强建议
密钥管理:
将RSA私钥存储在安全的地方(如HashiCorp Vault、AWS KMS)
定期轮换RSA密钥对
会话安全:
为会话设置TTL(生存时间)
使用HTTPS防止中间人攻击
添加请求签名验证
防重放攻击:
在请求中添加时间戳和随机数(nonce)
服务端校验请求时效性(如5分钟内有效)
性能优化:
使用连接池减少SSL/TLS握手开销
对于高并发场景,考虑使用国密算法(SM2/SM4)
监控与日志:
记录加密失败事件
监控加解密耗时
这套方案结合了RSA和AES的优势,既保证了密钥交换的安全性,又利用了对称加密的高效性,适合为第三方提供安全的API服务。
使用Redis替代内存存储会话密钥的方案
在之前的SessionManager实现中,我们使用了内存中的ConcurrentHashMap来存储会话密钥。在生产环境中,这会有以下问题:
应用重启后所有会话失效
无法在集群环境中共享会话数据
内存泄漏风险
下面是如何用Redis替代内存存储的完整方案:
一、改造后的SessionManager (Redis版)
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import .security.KeyPair;
import .security.NoSuchAlgorithmException;
import .util.Base64;
import .util.concurrent.TimeUnit;
@Component
public class SessionManager {
private final StringRedisTemplate redisTemplate;
private final KeyPair rsaKeyPair;
private static final String SESSION_KEY_PREFIX = "encrypt:session:";
private static final long SESSION_TTL_MINUTES = 30; // 会话30分钟过期
public SessionManager(StringRedisTemplate redisTemplate) throws NoSuchAlgorithmException {
this.redisTemplate = redisTemplate;
this.rsaKeyPair = EncryptUtils.generateRSAKeyPair();
}
// 获取RSA公钥(Base64编码)
public String getPublicKey() {
return Base64.getEncoder().encodeToString(rsaKeyPair.getPublic().getEncoded());
}
// 初始化会话
public String initSession(String sessionId, String encryptedClientAesKey) throws Exception {
// 1. 解密客户端AES密钥
String clientAesKey = EncryptUtils.rsaDecrypt(
encryptedClientAesKey,
rsaKeyPair.getPrivate()
);
// 2. 生成服务端AES密钥
String serverAesKey = EncryptUtils.generateAESKey();
// 3. 存储到Redis并设置TTL
String redisKey = SESSION_KEY_PREFIX + sessionId;
redisTemplate.opsForValue().set(
redisKey,
serverAesKey,
SESSION_TTL_MINUTES,
TimeUnit.MINUTES
);
// 4. 返回服务端AES密钥(用客户端AES密钥加密)
return EncryptUtils.aesEncrypt(serverAesKey, clientAesKey);
}
// 获取会话AES密钥
public String getSessionKey(String sessionId) {
String redisKey = SESSION_KEY_PREFIX + sessionId;
return redisTemplate.opsForValue().get(redisKey);
}
// 移除会话
public void removeSession(String sessionId) {
String redisKey = SESSION_KEY_PREFIX + sessionId;
redisTemplate.delete(redisKey);
}
// 续期会话
public void renewSession(String sessionId) {
String redisKey = SESSION_KEY_PREFIX + sessionId;
redisTemplate.expire(redisKey, SESSION_TTL_MINUTES, TimeUnit.MINUTES);
}
}
二、Redis配置
1. 添加Redis依赖
xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
2. 配置application.yml
yaml
spring:
redis:
host: ${REDIS_HOST:localhost}
port: ${REDIS_PORT:6379}
password: ${REDIS_PASSWORD:}
database: 0
timeout: 5000
lettuce:
pool:
max-active: 8
max-idle: 8
min-idle: 0
max-wait: -1ms
三、增强功能实现
1. 自动续期拦截器
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class SessionRenewInterceptor implements HandlerInterceptor {
private final SessionManager sessionManager;
public SessionRenewInterceptor(SessionManager sessionManager) {
this.sessionManager = sessionManager;
}
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
String sessionId = request.getHeader("X-Session-Id");
if (sessionId != null && sessionManager.getSessionKey(sessionId) != null) {
sessionManager.renewSession(sessionId);
}
return true;
}
}
2. 注册拦截器
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebConfig implements WebMvcConfigurer {
private final SessionManager sessionManager;
public WebConfig(SessionManager sessionManager) {
this.sessionManager = sessionManager;
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new SessionRenewInterceptor(sessionManager))
.addPathPatterns("/api/encrypt/**");
}
}
四、集群环境下的考虑
1. Redis集群配置
yaml
spring:
redis:
cluster:
nodes:
- redis-node1:6379
- redis-node2:6379
- redis-node3:6379
max-redirects: 3
2. 分布式锁实现(防止并发初始化)
public boolean tryInitSession(String sessionId, String encryptedClientAesKey) throws Exception {
String lockKey = SESSION_KEY_PREFIX + sessionId + ":lock";
String lockValue = UUID.randomUUID().toString();
try {
// 尝试获取锁
Boolean locked = redisTemplate.opsForValue().setIfAbsent(
lockKey,
lockValue,
10, // 锁10秒自动释放
TimeUnit.SECONDS
);
if (Boolean.TRUE.equals(locked)) {
// 获取锁成功,执行初始化
initSession(sessionId, encryptedClientAesKey);
return true;
}
return false;
} finally {
// 释放锁
if (lockValue.equals(redisTemplate.opsForValue().get(lockKey))) {
redisTemplate.delete(lockKey);
}
}
}
五、监控与维护
1. Redis会话监控脚本
bash
# 查看当前活跃会话数量
redis-cli --eval count_sessions.lua "encrypt:session:*" , 0
# count_sessions.lua
local pattern = ARGV[1]
local cursor = tonumber(ARGV[2])
local count = 0
repeat
local reply = redis.call("SCAN", cursor, "MATCH", pattern)
cursor = tonumber(reply[1])
local keys = reply[2]
count = count + #keys
until cursor == 0
return count
2. 会话清理策略
定期清理:设置合理的TTL自动过期
主动清理:实现管理接口清理无效会话
LRU策略:Redis可配置maxmemory-policy为allkeys-lru
六、性能优化建议
Pipeline批量操作:对于批量会话操作使用pipeline
本地缓存:高频访问的会话可在本地缓存(需处理一致性)
Redis数据结构优化:
对于大型会话考虑使用Hash存储
对小对象启用Redis压缩
这样改造后,系统获得了以下优势:
会话数据持久化,应用重启不丢失
支持水平扩展,多实例共享会话
自动过期机制防止内存泄漏
完善的监控和管理能力