[048][Crypto模块]Spring Boot 请求体自动解密:@Crypto 注解 + RequestBodyAdvice 实现

简介: 本文介绍基于Spring Boot的请求体自动解密方案:通过自定义`@Crypto`注解与`RequestBodyAdvice`,在Controller入参前透明解密RSA/SM2加密的JSON请求体,实现业务代码零侵入、算法可插拔、开关灵活的安全传输机制。(239字)

[048][Crypto模块]Spring Boot 请求体自动解密:@Crypto 注解 + RequestBodyAdvice 实现

在 Web 应用中,前端经常需要对请求体进行加密后传输,后端在进入 Controller 之前自动解密。Spring MVC 提供了 RequestBodyAdvice 接口,允许我们在请求体绑定到方法参数之前对 HttpInputMessage 进行拦截和修改。本文结合自定义注解 @Crypto,实现优雅的自动解密。

一、需求场景

假设前端使用 RSA 公钥加密了 JSON 请求体,后端收到的是一个密文字符串:

POST /api/user
Content-Type: text/plain

"encrypted_base64_string..."

我们希望 Controller 方法直接接收到解密后的 Java 对象:

@PostMapping("/user")
@Crypto(request = true)
public UserDto createUser(@RequestBody UserDto userDto) {
   
    // userDto 已经是解密后的对象
}

二、自定义注解 @Crypto

注解标记在方法上,用于声明该方法是否需要请求体解密以及响应加密(响应加密可后续扩展):

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Crypto {
   
    boolean response() default false;
    boolean request() default true;
}

三、实现 RequestBodyAdvice

CryptoRequestBodyAdvice 实现 RequestBodyAdvice 接口,并重写关键方法:

@RestControllerAdvice
@RequiredArgsConstructor
public class CryptoRequestBodyAdvice implements RequestBodyAdvice {
   
    private final CryptoProcessor cryptoProcessor; // 非对称加密处理器(如 RSA 或 SM2)

    @Override
    public boolean supports(MethodParameter parameter, Type targetType,
                            Class<? extends HttpMessageConverter<?>> converterType) {
   
        Crypto crypto = parameter.getMethodAnnotation(Crypto.class);
        return crypto != null && crypto.request();
    }

    @Override
    public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage,
                                           MethodParameter parameter,
                                           Type targetType,
                                           Class<? extends HttpMessageConverter<?>> converterType)
            throws IOException {
   
        // 1. 读取原始加密字符串
        String encrypted = StreamUtils.copyToString(inputMessage.getBody(), StandardCharsets.UTF_8);
        if (StringUtils.isBlank(encrypted)) {
   
            return inputMessage;
        }
        // 2. 去除可能的前后引号(前端传 JSON 字符串时可能带引号)
        encrypted = encrypted.replaceAll("^\"|\"$", "");
        // 3. 解密
        String decrypted = cryptoProcessor.decrypt(encrypted);
        // 4. 包装成新的 HttpInputMessage
        return new DecryptHttpInputMessage(inputMessage, decrypted.getBytes(StandardCharsets.UTF_8));
    }

    @Override
    public Object afterBodyRead(Object body, HttpInputMessage inputMessage,
                                MethodParameter parameter, Type targetType,
                                Class<? extends HttpMessageConverter<?>> converterType) {
   
        return body; // 直接返回
    }

    @Override
    public Object handleEmptyBody(Object body, HttpInputMessage inputMessage,
                                  MethodParameter parameter, Type targetType,
                                  Class<? extends HttpMessageConverter<?>> converterType) {
   
        return body;
    }

    // 内部类用于替换请求体
    public static class DecryptHttpInputMessage implements HttpInputMessage {
   
        private final HttpInputMessage original;
        private final byte[] data;

        public DecryptHttpInputMessage(HttpInputMessage original, byte[] data) {
   
            this.original = original;
            this.data = data;
        }

        @Override
        public InputStream getBody() {
   
            return new ByteArrayInputStream(data);
        }

        @Override
        public HttpHeaders getHeaders() {
   
            return original.getHeaders();
        }
    }
}

四、获取公钥的端点

前端需要公钥来进行加密,因此提供一个公开的 GET 接口返回公钥 Hex 字符串:

@RestController
@RequestMapping("/api/crypto")
@RequiredArgsConstructor
public class CryptoEndpoint {
   
    private final CryptoProperties properties;

    @GetMapping("publicKey")
    public String getPublicKey() {
   
        CryptoProcessor processor = CryptoProcessorFactory.instance.findProcessor(
            properties.getAsymmetricCryptoStrategy().getCategory());
        return processor.getSecretKey().publicKeyHex();
    }
}

前端示例流程:

  1. 先调用 /api/crypto/publicKey 获取公钥。
  2. 使用公钥加密请求体数据(如 JSON 字符串)。
  3. 在 POST 请求的 body 中发送密文,并确保 Content-Typetext/plain 或能被读取为字符串即可。
  4. 后端自动解密,Controller 正常接收 Java 对象。

五、注意事项与最佳实践

  1. Content-Type 的处理:本实现假设请求体是纯文本(text/plain)或者 Spring 能将其读取为字符串。如果你希望仍然使用 application/json 但 body 是密文字符串,可能会遇到类型转换问题。一个常见方案是自定义一个 @EncryptedRequestBody 参数解析器,或者约定前端将密文放在 JSON 对象的某个字段中。

  2. 性能考虑:解密操作可能较慢(尤其非对称加密),建议只在必要的接口上使用 @Crypto,并通过 supports 方法过滤。

  3. 错误处理:解密失败时应抛出异常,由全局异常处理器返回友好提示。

  4. 响应加密:类似地可以实现 ResponseBodyAdvice 对返回值进行加密,本文未展示,但框架的 @Crypto(response = true) 预留了扩展点。

  5. 日志记录:在 beforeBodyRead 中打印 debug 日志,方便排查问题,但注意不要打印明文敏感数据。

六、总结

通过 RequestBodyAdvice + 自定义注解,我们实现了近乎透明的请求体自动解密能力,具有以下优点:

  • 无侵入:Controller 代码完全不需要关心加密逻辑,保持纯净的业务代码。
  • 可配置:通过 @Crypto(request = true) 灵活开关。
  • 算法无关:解密处理器 CryptoProcessor 可以根据配置选用 RSA、SM2 等算法,前端获取对应的公钥即可。
  • 易于测试:单元测试时可以关闭解密功能,直接发送明文。

这种模式非常适合需要接口加密传输的内部系统或暴露到公网的 API 网关,能够在不影响业务开发的前提下提升安全性。

目录
相关文章
|
1月前
|
算法 NoSQL Java
[022][数据模块]基于雪花算法的 MyBatis-Plus 主键生成器设计与实现
本项目基于Hutool雪花算法,为MyBatis-Plus定制轻量级分布式主键生成器,含`SnowflakeUtils`(单例+JVM参数配置)、`DefaultIdentifierGenerator`(无缝接入MP)及自动配置类,支持全局有序、高性能ID生成,已落地多个微服务系统。(239字)
297 1
|
1月前
|
SQL Java 数据库连接
[026][数据模块]基于 MyBatis Plus 的企业级数据访问框架设计与实现
本文介绍基于MyBatis Plus二次封装的企业级数据访问框架,支持多租户隔离、分页、乐观锁、SQL防攻击及审计字段自动填充。通过有序拦截器链、条件化配置与开放扩展点,实现可插拔、易维护的统一数据访问能力。(239字)
111 2
|
30天前
|
缓存 NoSQL 搜索推荐
[028][缓存模块]命名缓存:多级个性化缓存配置的设计与实现
本文介绍“命名缓存”(Named Cache)设计方案:通过配置文件为不同缓存名称(如userCache、productCache)独立设置TTL、空值缓存、键前缀、容量等策略,支持Redis与Caffeine双后端。配置即生效,无需修改代码,兼顾灵活性与可维护性。(239字)
183 0
|
1月前
|
前端开发 NoSQL Java
[027][Web模块]基于 Spring MVC 的 API 签名校验拦截器设计与实现
本文介绍基于Spring MVC的API签名校验拦截器,支持HmacSHA256签名、时间窗校验、nonce防重放及密钥动态加载,通过`@RequiredSignature`注解无侵入集成,具备高扩展性与生产可用性。(239字)
130 1
|
1月前
|
算法 安全 Java
[047][Crypto模块]基于 Hutool 的常见加解密算法封装与密钥自动生成
本文基于Hutool封装统一加解密框架,提供AES/RSA/SM2/SM4/HMAC等算法的`CryptoProcessor`标准接口,支持密钥自动生成与动态切换,解耦业务代码,兼顾国密合规与易用性,提升安全性与可测试性。(239字)
89 1
|
1月前
|
人工智能 算法 数据挖掘
TID质量竞争大会分享议题|从传统质量到算法评测的转型
AI时代,质量工作正从功能验证迈向算法效果评测。本届TID大会,多模态算法评测负责人罗军将分享《从传统质量到算法评测的转型》,剖析音视频、AIGC、多模态及Agent场景下的评测挑战、方法论演进与团队能力升级路径,为质量团队提供实战参考。
|
25天前
|
存储 缓存 NoSQL
[051][缓存模块]基于 StringRedisTemplate 的多租户 Key 隔离设计与实践——以 RedisBitmapUtils 为例
本文介绍基于StringRedisTemplate的多租户Redis Key隔离方案:通过自定义TenantStringRedisSerializer,在Key序列化时自动注入租户前缀,实现透明、低侵入的租户数据隔离。以RedisBitmapUtils为例,业务代码无需感知租户ID,所有操作自动适配,兼顾安全性与易用性。(239字)
101 0
|
1月前
|
设计模式 Java Spring
[046][Crypto模块]Spring Boot 自动配置进阶:按需装配加解密处理器
本文详解Spring Boot自动配置进阶实践:基于Crypto模块,通过`@ConditionalOnMissingBean`、`@EnableConfigurationProperties`等注解,实现加解密处理器的按需装配与灵活覆盖。涵盖配置属性绑定、多配置类拆分、条件加载(Web/非Web)、日志调试及Hutool集成,助力构建高可扩展Starter。
149 2
|
1月前
|
缓存 监控 安全
[025][Web模块]基于 Spring Boot 的请求日志过滤器设计与实现
本文介绍基于Spring Boot的可配置请求日志过滤器,通过自定义`WebHttpProperties`、扩展`CommonsRequestLoggingFilter`及自动配置类,支持时间戳、客户端信息、请求头/体记录、动态前缀等灵活配置,开箱即用,兼顾可维护性与生产安全性。(239字)
388 1
|
1月前
|
算法 Java 数据安全/隐私保护
[045][Crypto模块]设计一个可扩展的加解密框架:策略模式与工厂模式实战
本文基于Spring Boot,运用策略模式与工厂模式设计可扩展加解密框架:统一`CryptoProcessor`接口封装AES、RSA、SM2等算法;通过`CryptoProcessorFactory`按枚举类别自动注册/查找处理器;结合Spring自动装配,新增算法(如SM9)仅需实现接口并声明Bean,零侵入扩展。高内聚、低耦合、易维护。(239字)
131 2