SpringBoot中,接口加解密

发布于:2025-08-18 ⋅ 阅读:(13) ⋅ 点赞:(0)

1、背景

在项目做等保,要求需要对接口数据进行加密,所做了调用方发送请求前,先对明文加密,然后发送密文,被调用方收到数据后,先进行解密,然后再进行处理。返回的结果同样也可以加密,被调用方将需要返回的数据进行加密,然后将密文返回给调用方,调用方收到后,再进行解密便得到明文。

2、解决方案

使用AES的方式对数据加密

AES介绍

  1. 对称加密
    加密与解密使用同一密钥,效率高但需安全分发密钥。
  2. 分组密码
    将明文分割为固定128位(16字节)的块独立处理。
  3. 密钥长度灵活
    支持128位、192位、256位三种长度,安全性依次递增(AES-128/192/256)

利用RequestBodyAdviceAdapter,继承这个类重写其beforeBodyRead方法,完成请求在进入方法前对参数解密

RequestBodyAdviceAdapter介绍

RequestBodyAdviceAdapter 是 Spring Framework 中用于拦截和处理 **@RequestBody 注解参数**的核心组件,属于 Spring MVC 的全局增强机制。它通过 AOP 思想实现对请求体的统一预处理,避免在 Controller 中重复编写与业务无关的逻辑(如解密、校验、日志记录等)

方法

调用时机

用途

返回值

supports()

请求进入时优先判断

决定当前 Advice 是否生效(如按包路径、注解过滤)

true:生效;

false:跳过

beforeBodyRead()

请求体被 HttpMessageConverter

读取

修改原始请求体(如解密、字符编码转换)

自定义的 HttpInputMessage

afterBodyRead()

请求体转换为 Java 对象

修改对象(如参数校验、字段注入)

处理后的 Java 对象

handleEmptyBody()

请求体为空时

处理空请求体场景(如设置默认值)

替代空体的对象

在数据返回时候需要加密,用到ResponseBodyAdvice

ResponseBodyAdvice 介绍

ResponseBodyAdvice 是 Spring MVC(4.1+)及 Spring Boot 中用于全局拦截并定制化处理响应体的核心接口,通常与 @ControllerAdvice@RestControllerAdvice 配合使用。它允许开发者在控制器方法执行后、响应数据写入 HTTP 响应体之前,对返回的数据进行统一处理,适用于多种通用场景(如数据包装、脱敏、日志记录等)。

  1. 定位与触发时机
    • 作用阶段:在控制器方法执行完毕且返回值被 HttpMessageConverter 序列化之前介入。
    • 触发条件:仅对标注 @ResponseBody@RestController 的控制器方法生效。
  1. 核心方法

方法

作用

参数说明

supports()

判断当前响应是否需被处理(返回 true

则触发 beforeBodyWrite

returnType:控制器返回类型;
converterType:消息转换器类型。

beforeBodyWrite()

实际处理响应体,可修改或替换原始返回值

body:原始返回值;
request/response:HTTP 请求/响应对象;

作用方法指定方案

1.用到了@RestControllerAdvice这个注解指定扫描包路径来实现,这样就需要supports()方法直接返回true

@ControllerAdvice(basePackages = "cn.shenzhihe.collection.controller")

2.如果想指定具体方法,可以自定义注解方式,在supports()方法下指定注解

@Override
    public boolean supports(MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
      return methodParameter.hasMethodAnnotation(Decrypt.class);
    
    }

3.代码实现

指定配置类讲AES注册到ioc



@Configuration
@EnableConfigurationProperties(SecureProperties.class)
public class SecureConfiguration {
    @Autowired
    private SecureProperties secureProperties;

    @Bean
    public AES aes() {
        return SecureUtil.aes(this.secureProperties.getKey().getBytes(StandardCharsets.UTF_8));
    }
}

配置

@Getter
@Setter
@ConfigurationProperties("secure")
public class SecureProperties {
    /**
     * 秘钥(长度只能是 128、192或256位,一个普通字符是8位)
     */
    private String key;
}

yml

secure:
  key: 1234567890123456

入参加密



/**
 * 请求到达controller中的方法之前,会拦截标注有 @Decrypt 注解或者指定包下的方法,负责将原始请求中的密文转换为明文
 */
@ControllerAdvice(basePackages = "cn.shenzhihe.collection.controller")
public class DecryptRequestBodyAdvice extends RequestBodyAdviceAdapter {

    @Autowired
    private AES aes;

    @Override
    public boolean supports(MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
       // 注解方式
        //return methodParameter.hasMethodAnnotation(Decrypt.class);
        //路径方式直接返回true
        return true;
    }

    @Override
    public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) throws IOException {
        String encoding = "UTF-8";
        try {
            //①:获取http请求中原始的body
            String body = IOUtils.toString(inputMessage.getBody(), encoding);
            //②:解密body,使用AES算法解密,得到明文
            String decryptBody = aes.decryptStr(body);
            //将解密之后的body数据重新封装为HttpInputMessage作为当前方法的返回值
            InputStream inputStream = new ByteArrayInputStream(decryptBody.getBytes(encoding));
            return new HttpInputMessage() {
                @Override
                public InputStream getBody() throws IOException {
                    return inputStream;
                }
                @Override
                public HttpHeaders getHeaders() {
                    return inputMessage.getHeaders();
                }
            };
        } catch (Exception e) {
            // 如果解密失败,返回原始消息
            return inputMessage;
        }
    }
}


/**
 * 在结果返回给调用者之前,会拦截标注有@Encrypt 注解或者包下方法的方法,对接口的返回值进行处理,将其转换为密文
 */
@ControllerAdvice(basePackages = "cn.shenzhihe.collection.controller")
public class EncryptResponseBodyAdvice implements ResponseBodyAdvice<Object> {

    @Autowired
    private AES aes;

    private ObjectMapper objectMapper = new ObjectMapper();

    @Override
    public boolean supports(MethodParameter returnType, Class converterType) {
       // 注解方式
        //return methodParameter.hasMethodAnnotation(Decrypt.class);
        //路径方式直接返回true
        return true;
    }

    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
        if (body == null) {
            return body;
        }
        String result;
        if (body instanceof String) {
            result = (String) body;
        } else {
            //如果是对象,则转换为json字符串
            try {
                result = objectMapper.writeValueAsString(body);
            } catch (JsonProcessingException e) {
                throw new RuntimeException(e);
            }
        }
        //加密返回
        return this.aes.encryptHex(result);
    }
}

两个注解一起使用,入参解密,返回加密

注解



/**
 * 接口方法上添加该注解,这表示这个接口的参数是被加密的,进入方法之前,参数会自动被解密
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Decrypt {
}

/**
 * 接口方法上添加该注解,则返回的结果,会自动加密
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Encrypt {
}

4.测试

@RestController
@RequestMapping("/secure")
@Slf4j
public class SecureController {

    /**
     * 参数加密测试,需要在方法上标注 @Decrypt 注解
     *
     * @param body
     * @return
     */
   // @Decrypt
    @PostMapping("/decryptTest")
    public List<String> decryptTest(@RequestBody List<String> body) {
        log.info("参数加密测试,请求参数:{}", body);
        return body;
    }

   // @Encrypt
    @GetMapping("/encryptTest")
    public List<String> encryptTest() {
        List<String> encyptList = new ArrayList<>();
        encyptList.add("1");
        encyptList.add("2");
        return encyptList;
    }
}

返回密文

解析密文


网站公告

今日签到

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