软件编程
位置:首页>> 软件编程>> java编程>> SpringBoot拦截 器如何获取http请求参数

SpringBoot拦截 器如何获取http请求参数

作者:上帝爱吃苹果-Soochow  发布时间:2023-11-28 19:40:48 

标签:springboot,拦截,器,http请求

1.1、获取http请求参数是一种刚需

我想有的小伙伴肯定有过获取http请求的需要,比如想

  1. 前置获取参数,统计请求数据

  2. 做服务的接口签名校验

  3. 敏感接口监控日志

  4. 敏感接口防重复提交

等等各式各样的场景,这时你就需要获取 HTTP 请求的参数或者请求body,一般思路有两种,一种就是自定义个AOP去拦截目标方法,第二种就是使用 * 。整体比较来说,使用 * 更灵活些,因为每个接口的请求参数定义不同,使用AOP很难细粒度的获取到变量参数,本文主线是采用 * 来获取HTTP请求。

1.2、定义 * 获取请求

基于 spring-boot-starter-parent 2.1.9.RELEASE

看起来这个很简单,这里就直接上code,定义个 *


/**
* @author axin
* @summary HTTP请求 *
*/
@Slf4j
public class RequestInterceptor implements HandlerInterceptor {
@Override
 public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//获取请求参数
   String queryString = request.getQueryString();
   log.info("请求参数:{}", queryString);
//获取请求body
   byte[] bodyBytes = StreamUtils.copyToByteArray(request.getInputStream());
   String body = new String(bodyBytes, request.getCharacterEncoding());
log.info("请求体:{}", body);
   return true;
 }
}

然后把这个 * 配置一下中:


/**
* WebMVC配置,你可以集中在这里配置 * 、过滤器、静态资源缓存等
*/
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Override
 public void addInterceptors(InterceptorRegistry registry) {
   registry.addInterceptor(new RequestInterceptor()).addPathPatterns("/**");
 }
}

定义个接口测试一下


/**
* @author axin
* @summary 提交测试接口
*/
@Slf4j
@RestController
public class MyHTTPController {
@GetMapping("/v1/get")
 public void get(@RequestParam("one") String one,
         @RequestParam("two") BigDecimal number) {
   log.info("参数:{},{}", one, number);
 }
@PostMapping("/v1/post")
 public void check(@RequestBody User user) {
log.info("{}", JSON.toJSONString(user));
 }
}

GET请求获取请求参数示例:

SpringBoot拦截 器如何获取http请求参数

POST请求获取请求Body示例:

SpringBoot拦截 器如何获取http请求参数

我们发现 * 在获取HTTP请求的body时出现了 400 (Required request body is missing: public void com.axin.world.controller.MyHTTPController.check(com.axin.world.domain.User));同时也发现 * 竟然走了两遍,这又是咋回事呢?

SpringBoot拦截 器如何获取http请求参数

1.3、为什么 * 会重复调两遍呢?

其实是因为 tomcat截取到异常后就转发到/error页面,就在这个转发的过程中导致了springmvc重新开始DispatcherServlet的整个流程,所以 * 执行了两次,我们可以看下第二次调用时的url路径:

SpringBoot拦截 器如何获取http请求参数

1.4、ServletInputStream(CoyoteInputStream) 输入流无法重复调用

而之前出现的 Required request body is missing 错误 其实是ServletInputStream被读取后无法第二次再读取了,所以我们要把读取过的内容存下来,然后需要的时候对外提供可被重复读取的ByteArrayInputStream。

对于MVC的过滤器来说,我们就需要重写 ServletInputStream 的 getInputStream()方法。

1.5、自定义 HttpServletRequestWrapper

为了 重写 ServletInputStream 的 getInputStream()方法,我们需要自定义一个 HttpServletRequestWrapper :


/**
* @author Axin
* @summary 自定义 HttpServletRequestWrapper 来包装输入流
*/
public class AxinHttpServletRequestWrapper extends HttpServletRequestWrapper {
/**
  * 缓存下来的HTTP body
  */
 private byte[] body;
public AxinHttpServletRequestWrapper(HttpServletRequest request) throws IOException {
   super(request);
   body = StreamUtils.copyToByteArray(request.getInputStream());
 }
/**
  * 重新包装输入流
  * @return
  * @throws IOException
  */
 @Override
 public ServletInputStream getInputStream() throws IOException {
   InputStream bodyStream = new ByteArrayInputStream(body);
   return new ServletInputStream() {
@Override
     public int read() throws IOException {
       return bodyStream.read();
     }
/**
      * 下面的方法一般情况下不会被使用,如果你引入了一些需要使用ServletInputStream的外部组件,可以重点关注一下。
      * @return
      */
     @Override
     public boolean isFinished() {
       return false;
     }
@Override
     public boolean isReady() {
       return true;
     }
@Override
     public void setReadListener(ReadListener readListener) {
}
   };
 }
@Override
 public BufferedReader getReader() throws IOException {
   InputStream bodyStream = new ByteArrayInputStream(body);
   return new BufferedReader(new InputStreamReader(getInputStream()));
 }
}

然后定义一个 DispatcherServlet子类来分派 上面自定义的 AxinHttpServletRequestWrapper :


/**
* @author Axin
* @summary 自定义 DispatcherServlet 来分派 AxinHttpServletRequestWrapper
*/
public class AxinDispatcherServlet extends DispatcherServlet {
/**
  * 包装成我们自定义的request
  * @param request
  * @param response
  * @throws Exception
  */
 @Override
 protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
   super.doDispatch(new AxinHttpServletRequestWrapper(request), response);
 }
}

然后配置一下:


/**
* WebMVC配置,你可以集中在这里配置 * 、过滤器、静态资源缓存等
*/
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Override
 public void addInterceptors(InterceptorRegistry registry) {
   registry.addInterceptor(new RequestInterceptor()).addPathPatterns("/**");
 }
@Bean
 @Qualifier(DispatcherServletAutoConfiguration.DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
 public DispatcherServlet dispatcherServlet() {
   return new AxinDispatcherServlet();
 }
}

再调用一下 POST请求:

SpringBoot拦截 器如何获取http请求参数

请求成功!

1.5、总结一下 展望一下

如果你想对HTTP请求做些骚操作,那么前置获取HTTP请求参数是前提,为此文本给出了使用MVC * 获取参数的样例。

在获取HTTP Body 的时候,出现了 Required request body is missing 的错误,同时 * 还出现执行了两遍的问题,这是因为 ServletInputStream被读取了两遍导致的,tomcat截取到异常后就转发到 /error 页面 被 * 拦截到了, * 也就执行了两遍。

为此我们通过自定义 HttpServletRequestWrapper 来包装一个可被重读读取的输入流,来达到期望的拦截效果。

在获取到HTTP的请求参数后,我们可以前置做很多操作,比如常用的服务端接口签名验证,敏感接口防重复请求等等。

个人水平有限,如果文章有逻辑错误或表述问题还请指出,欢迎一起交流。

来源:https://www.cnblogs.com/keeya/p/13634015.html

0
投稿

猜你喜欢

手机版 软件编程 asp之家 www.aspxhome.com