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

发布于:2025-08-01 ⋅ 阅读:(21) ⋅ 点赞:(0)

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

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

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

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

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

完善的监控和管理能力


网站公告

今日签到

点亮在社区的每一天
去签到