当前位置: 首页 > web >正文

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压缩

这样改造后,系统获得了以下优势:

会话数据持久化,应用重启不丢失

支持水平扩展,多实例共享会话

自动过期机制防止内存泄漏

完善的监控和管理能力

http://www.xdnf.cn/news/16753.html

相关文章:

  • 图像处理控件Aspose.Imaging教程:使用 C# 编程将 CMX 转换为 PNG
  • 基于 Rust 和土木工程、设备故障诊断、混凝土养护、GPS追踪、供应链物流跟踪系统、地下水监测等领域的实例
  • Y型M12一分二连接器:高效稳定的数据传输解决方案
  • 涿州周边水系分布三维地图
  • MyBatis Plus Wrapper 详细分析与原理
  • 代码随想录day50图论1
  • [leetcode] 反转字符串中的单词
  • Cockpit管理服务器
  • 在 CentOS 系统上安装 Docker
  • 《超级秘密文件夹》密码遗忘?试用版/正式版找回教程(附界面操作步骤)
  • NAT技术与代理服务
  • web服务器nginx
  • sqLite 数据库 (3):以编程方式使用 sqLite,4 个函数,以及 sqLite 移植,合并编译
  • USB电源原理图学习笔记
  • 相亲小程序聊天与互动系统模块搭建
  • 基于定制开发开源AI智能名片S2B2C商城小程序的B站私域流量引流策略研究
  • 线性回归原理与进阶
  • Three.js实现银河螺旋星云粒子特效——原理、实现
  • 在 Cloudflare 平台上完整部署 GitHub 项目 MoonTV 实现免费追剧流程
  • 广泛分布于内侧内嗅皮层全层的速度细胞(speed cells)对NLP中的深层语义分析的积极影响和启示
  • 基于springboot/java/VUE的旅游管理系统/旅游网站的设计与实现
  • 枚举中间位置高级篇
  • UE5 打包Windows平台时无法找到SDK的解决方法
  • 远程Qt Creator中文输入解决方案
  • Flex布局面试常考的场景题目
  • python中的 @dataclass
  • 第4章唯一ID生成器——4.5 美团点评开源方案Leaf
  • 【22】C# 窗体应用WinForm ——定时器Timer属性、方法、实例应用,定时切换画面
  • 破解企业无公网 IP 难题:可行路径与实现方法?
  • 【MySQL基础篇】:MySQL表的约束常用类型以及实战示例