在日常开发中,有时候我们经常需要和第三方接口打交道,有时候是我们调用别人的第三方接口,有时候是别人在调用我们的第三方接口,那么为了调用接口的安全性,一般都会对传输的数据进行加密操作,如果每个接口都由我们自己去手动加密和解密,那么工作量太大而且代码冗余。那么有没有简单的方法,借助 spring 提供的 RequestBodyAdvice 和 ResponseBodyAdvice 可以实现解密和加密操作。
需求:
1,后台方法上如果有@Encrypt注解和@RequestBody修饰的方法,需要进行参数的解密
2,后台方法上如果有@Encrypt注解和@ResponseBody修饰的方法,需要进行参数的加密
3,加密和解密规则
加密:对返回的值中增加-encrypt值
解密:对传入的值中删除-encrypt值
注:
1,@Encrypt 为自定义的一个注解。
2,此处为了简单,就使用删除或增加-encrypt这个,实际情况下可以使用复杂的加解密规则
前置知识:
RequestBodyAdvice:在 sping 4.2 新加入的一个接口,它可以使用在 @RequestBody 或 HttpEntity 修改的参数之前进行参数的处理,比如进行参数的解密。
ResponseBodyAdvice:在 spring 4.1 新加入的一个接口,在消息体被HttpMessageConverter写入之前允许Controller 中 @ResponseBody修饰的方法或ResponseEntity调整响应中的内容,比如进行相应的加密。
功能实现:
1,编写加密注解类(Encrypt)
/** * 进行参数加密和解密 * * @author huan.fu * @date 2018/9/28 - 16:08 */ @Target({ElementType.PARAMETER, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Encrypt { }
2,编写RequestBodyAdvice接口实现类,实现数据的解密操作
/** * 解密数据 * * @author huan.fu * @date 2018/9/28 - 16:09 */ @Slf4j @RestControllerAdvice public class ParamEncryptRequestBodyAdvice implements RequestBodyAdvice { @Override public boolean supports(MethodParameter methodParameter, Type type, Class extends HttpMessageConverter>> aClass) { return methodParameter.hasParameterAnnotation(RequestBody.class); } @Override public Object handleEmptyBody(Object o, HttpInputMessage httpInputMessage, MethodParameter methodParameter, Type type, Class extends HttpMessageConverter>> aClass) { return o; } @Override public HttpInputMessage beforeBodyRead(HttpInputMessage httpInputMessage, MethodParameter methodParameter, Type type, Class extends HttpMessageConverter>> aClass) throws IOException { return new HttpInputMessage() { @Override public InputStream getBody() throws IOException { log.info("此处进行解密数据"); return new ByteArrayInputStream(IOUtils.toString(httpInputMessage.getBody()).replace("-encrypt", "").getBytes(StandardCharsets.UTF_8)); } @Override public HttpHeaders getHeaders() { return httpInputMessage.getHeaders(); } }; } @Override public Object afterBodyRead(Object o, HttpInputMessage httpInputMessage, MethodParameter methodParameter, Type type, Class extends HttpMessageConverter>> aClass) { return o; } }
3,编写ResponseBodyAdvice接口实现类,实现数据的加密操作
/** * 加密数据 * * @author huan.fu * @date 2018/9/28 - 16:19 */ @Slf4j @RestControllerAdvice public class ParamEncryptResponseBodyAdvice implements ResponseBodyAdvice { private final ObjectMapper objectMapper = new ObjectMapper(); @Override public boolean supports(MethodParameter returnType, Class converterType) { return returnType.hasMethodAnnotation(ResponseBody.class); } @Override public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) { log.info("此处进行加密数据"); if (null != body) { try { Map map = objectMapper.readValue(objectMapper.writeValueAsString(body), Map.class); map.forEach((key, value) -> map.put(key, value + "-encrypt")); return map; } catch (IOException e) { log.error("加密数据失败.", e); } } return body; } }
4,编写控制层进行测试
/** * 用户信息控制器 * * @author huan.fu * @date 2018/9/28 - 15:55 */ @RestController @RequestMapping("user-info") @Slf4j public class UserInfoController { /** * 添加用户实现返回值加密 * * @param userInfo * @return */ @PostMapping("add") @Encrypt public UserInfo add(@RequestBody UserInfo userInfo) { log.info("添加新用户:[{}]", userInfo); return userInfo; } /** * 修改实现获取的参数进行解密 * * @param userInfo * @return */ @PostMapping("update") public UserInfo update(@Encrypt @RequestBody UserInfo userInfo) { log.info("修改用户信息:[{}]", userInfo); return userInfo; } }
5,测试参数的解密操作
可以看到:参数中的-encrypt 传递后后台被后台自动截取了,这样就类似于解密操作。
6,测试返回值的加密操作
可以看到:返回的值后面都有一个 -encrypt ,这样就实现了类似于加密操作。