Java Spring
在日常开发中,有时候经常需要和第三方接口打交道,有时候是方调用别人的第三方接口,有时候是别人在调用我方的第三方接口,那么为了调用接口的安全性,一般都会对传输的数据进行加密操作,如果每个接口都手动加密和解密,那么工作量太大而且代码冗余。那么有没有简单的方法,借助 Spring 提供的 RequestBodyAdviceResponseBodyAdvice 可以实现解密和加密操作。

需求分析

  • 后台方法上如果有 @Encrypt 注解和 @RequestBody 修饰的方法,需要进行参数的解密
  • 后台方法上如果有 @Encrypt 注解和 @ResponseBody 修饰的方法,需要进行参数的加密
  • 加密和解密规则

加密:对返回的值中增加 -encrypt
解密:对传入的值中删除 -encrypt
注:

  • @Encrypt 为自定义的一个注解。
  • 此处为了简单,就使用删除或增加 -encrypt 这个,实际情况下可以使用复杂的加解密规则

    1、基本思路介绍

    RequestBodyAdvice:在 Sping 4.2 新加入的一个接口,它可以使用在 @RequestBodyHttpEntity 修改的参数之前进行参数的处理,比如进行参数的解密。
    ResponseBodyAdvice:在 Spring 4.1 新加入的一个接口,在消息体被 HttpMessageConverter 写入之前允许 Controller 中 @ResponseBody 修饰的方法或 ResponseEntity 调整响应中的内容,比如进行相应的加密。

    2、功能实现步骤

    1. 编写加密注解类(Encrypt)

    ```java @Target({ElementType.PARAMETER, ElementType.METHOD})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface Encrypt {

}

  1. <a name="q8eN8"></a>
  2. ### 2. 编写`RequestBodyAdvice`接口实现类,实现数据的解密操作
  3. ```java
  4. @Slf4j
  5. @RestControllerAdvice
  6. public class ParamEncryptRequestBodyAdvice implements RequestBodyAdvice {
  7. @Override
  8. public boolean supports(MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) {
  9. return methodParameter.hasParameterAnnotation(RequestBody.class);
  10. }
  11. @Override
  12. public Object handleEmptyBody(Object o, HttpInputMessage httpInputMessage, MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) {
  13. return o;
  14. }
  15. @Override
  16. public HttpInputMessage beforeBodyRead(HttpInputMessage httpInputMessage, MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) throws IOException {
  17. return new HttpInputMessage() {
  18. @Override
  19. public InputStream getBody() throws IOException {
  20. log.info("此处进行解密数据");
  21. return new ByteArrayInputStream(IOUtils.toString(httpInputMessage.getBody()).replace("-encrypt", "").getBytes(StandardCharsets.UTF_8));
  22. }
  23. @Override
  24. public HttpHeaders getHeaders() {
  25. return httpInputMessage.getHeaders();
  26. }
  27. };
  28. }
  29. @Override
  30. public Object afterBodyRead(Object o, HttpInputMessage httpInputMessage, MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) {
  31. return o;
  32. }
  33. }

3. 编写ResponseBodyAdvice接口实现类,实现数据的加密操作

  1. @Slf4j
  2. @RestControllerAdvice
  3. public class ParamEncryptResponseBodyAdvice implements ResponseBodyAdvice {
  4. private final ObjectMapper objectMapper = new ObjectMapper();
  5. @Override
  6. public boolean supports(MethodParameter returnType, Class converterType) {
  7. return returnType.hasMethodAnnotation(ResponseBody.class);
  8. }
  9. @Override
  10. public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
  11. log.info("此处进行加密数据");
  12. if (null != body) {
  13. try {
  14. Map map = objectMapper.readValue(objectMapper.writeValueAsString(body), Map.class);
  15. map.forEach((key, value) -> map.put(key, value + "-encrypt"));
  16. return map;
  17. } catch (IOException e) {
  18. log.error("加密数据失败.", e);
  19. }
  20. }
  21. return body;
  22. }
  23. }

4. 编写控制层进行测试

  1. @RestController
  2. @RequestMapping("user-info")
  3. @Slf4j
  4. public class UserInfoController {
  5. /**
  6. * 添加用户实现返回值加密
  7. *
  8. * @param userInfo
  9. * @return
  10. */
  11. @PostMapping("add")
  12. @Encrypt
  13. public UserInfo add(@RequestBody UserInfo userInfo) {
  14. log.info("添加新用户:[{}]", userInfo);
  15. return userInfo;
  16. }
  17. /**
  18. * 修改实现获取的参数进行解密
  19. *
  20. * @param userInfo
  21. * @return
  22. */
  23. @PostMapping("update")
  24. public UserInfo update(@Encrypt @RequestBody UserInfo userInfo) {
  25. log.info("修改用户信息:[{}]", userInfo);
  26. return userInfo;
  27. }
  28. }

5. 测试参数的解密操作

2021-05-26-15-41-02-326891.gif
可以看到:参数中的 -encrypt 传递后后台被后台自动截取了,这样就类似于解密操作。

6. 测试返回值的加密操作

2021-05-26-15-41-02-603146.gif
可以看到:返回的值后面都有一个 -encrypt, 这样就模拟实现了类似于加密操作。