如何Spring项目中接口请求参数名称正确性校验?

一般情况下,接口参数校验只会校验参数值是否正确,例如值不能为空,字符串长度,数值范围等,可以通过javax.validation.constraints包下提供的注解类实现。但是在特殊场景下,尤其是接口对公网提供访问时,为了确保接口安全,我们会加强校验。也就是不只是校验参数值是否符合规范,也会对调用方传入的参数名称进行校验,如果传入的参数不在接口文档约定的范围内,进行拦截,并提示调用方参数名不被服务端接受。

实现本需求需要借助Spring中的两个类RequestBodyAdvice,ResponseBodyAdvice。

一、使用场景

参数过滤,数据加解密,日志打印等和业务逻辑无关的全局统一处理场景

二、作用范围

RequestBodyAdvice仅对使用了@RequestBody注解的方法生效 , 因为它原理上还是AOP , 所以GET方法是不会操作的。

三、实现代码

1、定义一个注解类,指明哪些接口方法需要做参数名称的强校验,方法上不加该注解,Spring做请求报文装配时,会自动把不被识别的参数过滤掉

1 @Target(ElementType.METHOD)
2 @Retention(RetentionPolicy.RUNTIME)
3 @Documented
4 public @interface ParamCheckJson {
5 
6 }

2、测试的controller类

 1 @RestController
 2 @RequestMapping("jackson/test")
 3 public class JacksonTestController {
 4 
 5     @PostMapping("complexTest")
 6     public RpcResponse<BookRpcBean> complexTest(@RequestBody RpcRequest<ComplexRequest> request) {
 7 
 8         return new RpcResponse("0000", "success");
 9     }
10 
11     @PostMapping("complexTestUnknown")
12     @ParamCheckJson
13     public RpcResponse<BookRpcBean> complexTestUnknown(@RequestBody RpcRequest<ComplexRequest> request) {
14 
15         try {
16             return new RpcResponse("0000", "success");
17         } catch (VerifyRequestBodyException e) {
18             return new RpcResponse("9999", e.getMessage());
19         } catch (Exception e) {
20             return new RpcResponse("9999", "系统异常");
21         }
22     }
23 
24 }

3、请求参数校验切面VerifyRequestBodyAdvice类

注意supports方法,这里很关键,supports返回true开启,采用调用下面的beforeBodyRead,afterBodyRead方法,返回false不开启。

通过methodParameter.getMethod().isAnnotationPresent(ParamCheckJson.class)判断controller里的方法是否加了ParamCheckJson注解,是的话执行强校验逻辑。

第34行代码是最关键的地方,我们利用jackson强校验机制来完成参数名称校验,下面列出了JsonTestUtil的关键代码。

 1 @RestControllerAdvice
 2 public class VerifyRequestBodyAdvice implements RequestBodyAdvice {
 3     @Override
 4     public boolean supports(MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
 5         return methodParameter.getMethod().isAnnotationPresent(ParamCheckJson.class);
 6     }
 7 
 8     @Override
 9     public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) throws IOException {
10         return new VerifyHttpInputMessage(inputMessage, targetType);
11     }
12 
13     @Override
14     public Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
15         return body;
16     }
17 
18     @Override
19     public Object handleEmptyBody(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
20         return body;
21     }
22 
23     class VerifyHttpInputMessage implements HttpInputMessage {
24         private HttpHeaders headers;
25         private InputStream body;
26 
27         public VerifyHttpInputMessage(HttpInputMessage inputMessage, Type targetType) throws IOException {
28             inputMessage.getHeaders().setContentType(MediaType.valueOf("application/json;charset=UTF-8"));
29             String data = IOUtils.toString(inputMessage.getBody());
30 
31             JavaType javaType = JsonTestUtil.getJavaType(targetType);
32 
33             try {
34                 Object object = JsonTestUtil.readValue(data, javaType);
35             } catch (Exception e) {
36                 throw new VerifyRequestBodyException(ExceptionCode.SYS_ERROR, "非法参数名称"+e.getMessage());
37             }
38 
39 
40             this.headers = inputMessage.getHeaders();
41             this.body = IOUtils.toInputStream(data, "UTF-8");
42         }
43 
44         @Override
45         public InputStream getBody() throws IOException {
46             return body;
47         }
48 
49         @Override
50         public HttpHeaders getHeaders() {
51             return headers;
52         }
53     }
54 }

4、JsonTestUtil关键代码

 1 public class JsonTestUtil {
 2     private static final ObjectMapper mapper = new ObjectMapper();
 3 
 4     static {
 5         // 忽略大小写
 6         mapper.configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, true);
 7         // 该特性决定了当遇到未知属性,是否应该抛出一个JsonMappingException异常。
 9         mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, true);
10         mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
11     }
12 
13     public static JavaType getJavaType(Type type) {
14         return mapper.getTypeFactory().constructType(type);
15     }
16 
17     public static <T> T readValue(String json, JavaType valueType)
18             throws IOException, JsonParseException, JsonMappingException {
19         if (null == json || json.isEmpty()) {
20             return null;
21         }
22         return mapper.readValue(json, valueType);
23     }
24 }

 

5、返回统一异常处理ExceptionResponseBodyAdvice类,为什么要在这里做异常处理,因为在VerifyRequestBodyAdvice中抛出的自定义异常在controller里面是无法捕获的,因为抛出VerifyRequestBodyException后,请求无法到达complexTestUnknown方法里面。

 1 @RestControllerAdvice
 2 public class ExceptionResponseBodyAdvice implements ResponseBodyAdvice {
 3     @Override
 4     public boolean supports(MethodParameter returnType, Class converterType) {
 5         return false;
 6     }
 7 
 8     @Override
 9     public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
10         return null;
11     }
12 
13     @ExceptionHandler(value = VerifyRequestBodyException.class)
14     @ResponseBody
15     public RpcResponse handlerGlobeException(HttpServletRequest request, VerifyRequestBodyException exception) {
16         return new RpcResponse<>(exception.getCode().value(), exception.getMessage());
17     }
18 }

6、测试

最后通过postman发起调用,返回结果如下

1 {
2     "code": "9999",
3     "message": "非法参数名称Unrecognized field \"aaa\",...... not marked as ignorable ......)",
5 }

 

posted @ 2023-08-30 15:06  shileishmily  阅读(92)  评论(0编辑  收藏  举报