SpringBoot 实现 RAS+AES 自动接口解密

SpringBoot 实现 RSA + AES 自动接口解密方案

在实际项目中,为了保证接口数据传输的安全性,常常需要采用混合加密方案。下面我将详细介绍如何在SpringBoot中实现RSA非对称加密传输AES密钥,然后使用AES对称加密解密请求体的完整方案。

一、方案设计

  1. 加密流程

客户端生成随机AES密钥

使用RSA公钥加密AES密钥

使用AES密钥加密请求数据

将加密后的AES密钥和加密数据一起传输

  1. 解密流程

服务端使用RSA私钥解密获取AES密钥

使用AES密钥解密请求体

处理业务逻辑

响应时同样使用AES加密返回数据

二、核心实现

  1. 添加依赖

xml

<!-- 加密相关 -->

<dependency>

<groupId>org.bouncycastle</groupId>

<artifactId>bcpkix-jdk15on</artifactId>

<version>1.68</version>

</dependency>

  1. 加密工具类

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);

}

}

  1. 自动解密注解

@Target(ElementType.METHOD)

@Retention(RetentionPolicy.RUNTIME)

public @interface DecryptRequest {

boolean value() default true;

}

  1. 请求解密拦截器

@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);

}

}

  1. 响应加密拦截器

@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);

}

}

}

  1. 控制器使用示例

@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加密数据

  1. 接口设计

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>

  1. 加密工具类

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);

}

}

  1. 会话管理组件

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);

}

}

  1. 控制器实现

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)

);

}

}

  1. 全局异常处理

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())

);

}

}

  1. 配置类

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密钥加密数据

  1. 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>

  1. 配置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;

}

}

  1. 注册拦截器

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

  1. 分布式锁实现(防止并发初始化)

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

  1. 会话清理策略

定期清理:设置合理的TTL自动过期

主动清理:实现管理接口清理无效会话

LRU策略:Redis可配置maxmemory-policy为allkeys-lru

六、性能优化建议

Pipeline批量操作:对于批量会话操作使用pipeline

本地缓存:高频访问的会话可在本地缓存(需处理一致性)

Redis数据结构优化:

对于大型会话考虑使用Hash存储

对小对象启用Redis压缩

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

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

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

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

完善的监控和管理能力

相关推荐
_oP_i2 小时前
RabbitMQ 队列配置设置 RabbitMQ 消息监听器的并发消费者数量java
java·rabbitmq·java-rabbitmq
Monkey-旭2 小时前
Android Bitmap 完全指南:从基础到高级优化
android·java·人工智能·计算机视觉·kotlin·位图·bitmap
我爱996!2 小时前
SpringMVC——响应
java·服务器·前端
小宋10213 小时前
多线程向设备发送数据
java·spring·多线程
大佐不会说日语~4 小时前
Redis高频问题全解析
java·数据库·redis
寒水馨4 小时前
Java 17 新特性解析与代码示例
java·开发语言·jdk17·新特性·java17
启山智软4 小时前
选用Java开发商城的优势
java·开发语言
鹦鹉0074 小时前
SpringMVC的基本使用
java·spring·html·jsp
R cddddd4 小时前
Maven模块化开发与设计笔记
java·maven
一勺-_-4 小时前
全栈:Maven的作用是什么?本地仓库,私服还有中央仓库的区别?Maven和pom.xml配置文件的关系是什么?
xml·java·maven