P04 | 全局请求加密:RequestBodyAdvice 实现原理与完整代码

张开发
2026/6/8 19:13:17 15 分钟阅读
P04 | 全局请求加密:RequestBodyAdvice 实现原理与完整代码
P04 | 全局请求加密RequestBodyAdvice 实现原理与完整代码付费文章| 第一阶段环境与架构核心思路AOP 切面透明加解密我们不想在每个 Controller 方法里手动解密那样太麻烦了。Spring MVC 提供了RequestBodyAdvice和ResponseBodyAdvice可以在请求体被解析之前和响应体被序列化之后插入自定义逻辑——这正是我们需要的。HTTP 请求 → RequestBodyAdvice解密→ Controller → ResponseBodyAdvice加密→ HTTP 响应加密数据包格式前端发送的请求体格式{ encryptKey: Base64(RSA加密后的AES密钥), encryptData: Base64(AES加密后的原始请求体), sign: MD5(SHA1(原始请求体内容)) }后端完整实现1. 加密请求数据类data class EncryptRequest( val encryptKey: String, // RSA 加密的 AES 密钥 val encryptData: String, // AES 加密的请求体 val sign: String? null // 签名可选 )2. RequestBodyAdvice解密拦截器RestControllerAdvice class DecryptRequestBodyAdvice( private val objectMapper: ObjectMapper, private val rsaUtil: RSAUtil ) : RequestBodyAdviceAdapter() { // 对哪些 Controller 生效全部 override fun supports( methodParameter: MethodParameter, targetType: Type, converterType: Classout HttpMessageConverter* ): Boolean true override fun beforeBodyRead( inputMessage: HttpInputMessage, parameter: MethodParameter, targetType: Type, converterType: Classout HttpMessageConverter* ): HttpInputMessage { // 读取原始请求体 val rawBody inputMessage.body.bufferedReader().readText() // 空请求体直接放行 if (rawBody.isBlank()) { return inputMessage } return try { // 解析加密请求 val encryptRequest objectMapper.readValue(rawBody, EncryptRequest::class.java) // 1. RSA 解密获取 AES 密钥 val aesKey rsaUtil.decrypt(encryptRequest.encryptKey) // 2. AES 解密获取原始请求体 val decryptedBody AESUtil.decrypt(encryptRequest.encryptData, aesKey) // 3. 包装为新的 HttpInputMessage DecryptedHttpInputMessage(inputMessage, decryptedBody) } catch (e: Exception) { // 解密失败可能是明文请求开发环境或格式错误 // 开发环境放行生产环境可以抛异常 inputMessage } } } // 包装解密后的请求体 class DecryptedHttpInputMessage( private val original: HttpInputMessage, private val decryptedBody: String ) : HttpInputMessage { override fun getHeaders() original.headers override fun getBody() decryptedBody.byteInputStream() }3. ResponseBodyAdvice加密拦截器RestControllerAdvice class EncryptResponseBodyAdvice( private val objectMapper: ObjectMapper ) : ResponseBodyAdviceAny { override fun supports( returnType: MethodReturnType, converterType: Classout HttpMessageConverter* ): Boolean true override fun beforeBodyWrite( body: Any?, returnType: MethodReturnType, selectedContentType: MediaType, selectedConverterType: Classout HttpMessageConverter*, request: ServerHttpRequest, response: ServerHttpResponse ): Any? { if (body null) return null // 从请求头获取客户端的 AES 密钥前端在请求头里带上 val aesKey request.headers.getFirst(X-AES-Key) ?: return body // 将响应体转为 JSON然后 AES 加密 val jsonBody objectMapper.writeValueAsString(body) val encryptedBody AESUtil.encrypt(jsonBody, aesKey) // 返回加密后的响应 return mapOf(encryptData to encryptedBody) } }AES 工具类object AESUtil { fun encrypt(data: String, key: String): String { val secretKey SecretKeySpec(key.toByteArray(Charsets.UTF_8), AES) val cipher Cipher.getInstance(AES/ECB/PKCS5Padding) cipher.init(Cipher.ENCRYPT_MODE, secretKey) val encrypted cipher.doFinal(data.toByteArray(Charsets.UTF_8)) return Base64.getEncoder().encodeToString(encrypted) } fun decrypt(encryptedData: String, key: String): String { val secretKey SecretKeySpec(key.toByteArray(Charsets.UTF_8), AES) val cipher Cipher.getInstance(AES/ECB/PKCS5Padding) cipher.init(Cipher.DECRYPT_MODE, secretKey) val decoded Base64.getDecoder().decode(encryptedData) val decrypted cipher.doFinal(decoded) return String(decrypted, Charsets.UTF_8) } }RSA 工具类Component class RSAUtil( Value(\${pikachu.rsa.private-key}) private val privateKeyStr: String, Value(\${pikachu.rsa.public-key}) private val publicKeyStr: String ) { // 用私钥解密服务端解密客户端加密的数据 fun decrypt(encryptedData: String): String { val keyBytes Base64.getDecoder().decode(privateKeyStr) val keySpec PKCS8EncodedKeySpec(keyBytes) val privateKey KeyFactory.getInstance(RSA).generatePrivate(keySpec) val cipher Cipher.getInstance(RSA/ECB/PKCS1Padding) cipher.init(Cipher.DECRYPT_MODE, privateKey) val decoded Base64.getDecoder().decode(encryptedData) return String(cipher.doFinal(decoded)) } // 返回公钥给前端 fun getPublicKey(): String publicKeyStr }一个不加密的接口怎么处理有些接口不需要加密比如获取 RSA 公钥本身// 在 Controller 方法上加注解跳过加解密 GetMapping(/public-key) SkipEncrypt // 自定义注解告诉 Advice 跳过此接口 fun getPublicKey(): MapString, String { return mapOf(publicKey to rsaUtil.getPublicKey()) }// 在 Advice 的 supports 方法中检查注解 override fun supports(...): Boolean { val skipEncrypt parameter.method?.getAnnotation(SkipEncrypt::class.java) return skipEncrypt null // 没有 SkipEncrypt 注解才需要加解密 }下一篇P05 → JWT Token 鉴权AuthInterceptor 完整实现

更多文章