xx项目有于安全问题,需要对接口整体进行加密处理,我们怎么处理呢。
和产品、前端讨论需求后,梳理了相关技术方案,主要的需求点如下:
尽量少改动,不影响之前的业务逻辑;
考虑到时间紧迫性,可采用对称性加密方式,服务需要对接安卓、IOS、H5三端,另外考虑到H5端存储密钥安全性相对来说会低一些,故分针对H5和安卓、IOS分配两套密钥;
要兼容低版本的接口,后面新开发的接口可不用兼容;
接口有GET和POST两种接口,需要都要进行加解密;
需求解析:
服务端、客户端和H5统一拦截加解密,网上有成熟方案,也可以按其他服务中实现的加解密流程来搞;
使用AES放松加密,考虑到H5端存储密钥安全性相对来说会低一些,故分针对H5和安卓、IOS分配两套密钥;
本次涉及客户端和服务端的整体改造,经讨论,新接口统一加 /secret/ 前缀来区分
那我们可以使用 @ControllerAdvice + RequestBodyAdvice/ResponseBodyAdvice 轻松实现
@ControllerAdvice / @RestControllerAdvice 区别?
@ControllerAdvice + @ExceptionHandler来实现全局异常捕获;陌生在于你除了copy代码时看到过外,自己似乎从来没有真正使用过它。
在前面关于@ModelAttribute和@InitBinder 的相关文章中其实和这个注解是打过照面的:在此注解标注的类上使用@InitBinder等注解可以使得它对"全局"生效实现统一的控制。本文将把@ControllerAdvice此注解作为重点进一步的去了解它的使用以及工作机制。
此类的命名是很有信息量的:Controller的Advice通知。关于Advice的含义,熟悉AOP相关概念的同学就不会陌生了,因此可以看到它整体上还是个AOP的设计思想,只是实现方式不太一样而已。
@ControllerAdvice使用AOP思想可以这么理解:此注解对目标Controller的通知是个环绕通知,织入的方式是注解方式,增强器是注解标注的方法。如此就很好理解@ControllerAdvice搭配@InitBinder/@ModelAttribute/@ExceptionHandler起到的效果喽~
官方doc说它可以和如上我指出的三个注解的一起使用。关于它的使用我总结有如下注意事项:
@ControllerAdvice只需要标注上即可,Spring MVC会在容器里自动探测到它(请确保能被扫描到,否则无效哦~)
若有多个@ControllerAdvice可以使用@Order或者Ordered接口来控制顺序
basePackageClasses属性最终也是转换为了basePackages拿去匹配的
RequestBodyAdvice/ResponseBodyAdvice
顾名思义,它们和@RequestBody和@ResponseBody有关,ResponseBodyAdvice是Spring4.1推出的,另外一个是4.2后才有。它哥俩和@ControllerAdvice一起使用会有很好的化学反应
RequestBodyAdvice
官方解释为:允许body体转换为对象之前进行自定义定制;也允许该对象作为实参传入方法之前对其处理。
- public interface RequestBodyAdvice {
-
- // 第一个调用的。判断当前的拦截器(advice是否支持)
- // 注意它的入参有:方法参数、目标类型、所使用的消息转换器等等
- boolean supports(MethodParameter methodParameter, Type targetType, Class extends HttpMessageConverter>> converterType);
-
- // 如果body体木有内容就执行这个方法(后面的就不会再执行喽)
- Object handleEmptyBody(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class extends HttpMessageConverter>> converterType);
-
- // 重点:它在body被read读/转换**之前**进行调用的
- HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class extends HttpMessageConverter>> converterType) throws IOException;
-
- // 它在body体已经转换为Object后执行。so此时都不抛出IOException了嘛~
- Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class extends HttpMessageConverter>> converterType);
-
- }
它的内置实现有这些:
RequestResponseBodyAdviceChain比较特殊,放在后面重点说明。RequestBodyAdviceAdapter没啥说的,因此主要看看JsonViewRequestBodyAdvice这个实现。
JsonViewRequestBodyAdvice
Spring MVC的内置实现,它支持的是Jackson的com.fasterxml.jackson.annotation.@JsonView这个注解,@JsonView一般用于标注在HttpEntity/@RequestBody上,来决定处理入参的哪些key。
该注解指定的反序列视图将传递给MappingJackson2HttpMessageConverter,然后用它来反序列化请求体(从而做对应的过滤)。
- // @since 4.2
- public class JsonViewRequestBodyAdvice extends RequestBodyAdviceAdapter {
-
- // 处理使用的消息转换器是AbstractJackson2HttpMessageConverter类型
- // 并且入参上标注有@JsonView注解的
- @Override
- public boolean supports(MethodParameter methodParameter, Type targetType, Class extends HttpMessageConverter>> converterType) {
- return (AbstractJackson2HttpMessageConverter.class.isAssignableFrom(converterType) &&
- methodParameter.getParameterAnnotation(JsonView.class) != null);
- }
-
- // 显然这里实现的beforeBodyRead这个方法:
- // 它把body最终交给了MappingJacksonInputMessage来反序列处理消息体
- // 注意:@JsonView能处理这个注解。也就是说能指定把消息体转换成指定的类型,还是比较实用的
- // 可以看到当标注有@jsonView注解后 targetType就没啥卵用了
- @Override
- public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter methodParameter, Type targetType, Class extends HttpMessageConverter>> selectedConverterType) throws IOException {
- JsonView ann = methodParameter.getParameterAnnotation(JsonView.class);
- Assert.state(ann != null, "No JsonView annotation");
-
- Class>[] classes = ann.value();
- // 必须指定class类型,并且有且只能指定一个类型
- if (classes.length != 1) {
- throw new IllegalArgumentException("@JsonView only supported for request body advice with exactly 1 class argument: " + methodParameter);
- }
- // 它是一个InputMessage的实现
- return new MappingJacksonInputMessage(inputMessage.getBody(), inputMessage.getHeaders(), classes[0]);
- }
-
- }
ResponseBodyAdvice
它允许在@ResponseBody/ResponseEntity标注的处理方法上在用HttpMessageConverter在写数据之前做些什么。
- // @since 4.1 泛型T:body类型
- public interface ResponseBodyAdvice
{ - boolean supports(MethodParameter returnType, Class extends HttpMessageConverter>> converterType);
- @Nullable
- T beforeBodyWrite(@Nullable T body, MethodParameter returnType, MediaType selectedContentType, Class extends HttpMessageConverter>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response);
- }
@JsonView的使用:它可以放入参时接收指定的字段;也可以让返回值中敏感字段(如密码、盐值等)不予返回,可做到非常灵活的配置和管理,实现一套代码多处使用的目的,提高集成程度
需要注意的是:xxxBodyAdvice虽然使用方便,但是它的普适性还是没有HandlerInterceptor那么强的,下面我列出使用它的几点局限/限制