SpringBoot拦截 器如何获取http请求参数
作者:上帝爱吃苹果-Soochow 发布时间:2023-11-28 19:40:48
1.1、获取http请求参数是一种刚需
我想有的小伙伴肯定有过获取http请求的需要,比如想
前置获取参数,统计请求数据
做服务的接口签名校验
敏感接口监控日志
敏感接口防重复提交
等等各式各样的场景,这时你就需要获取 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请求获取请求参数示例:
POST请求获取请求Body示例:
我们发现 * 在获取HTTP请求的body时出现了 400 (Required request body is missing: public void com.axin.world.controller.MyHTTPController.check(com.axin.world.domain.User));同时也发现 * 竟然走了两遍,这又是咋回事呢?
1.3、为什么 * 会重复调两遍呢?
其实是因为 tomcat截取到异常后就转发到/error页面,就在这个转发的过程中导致了springmvc重新开始DispatcherServlet的整个流程,所以 * 执行了两次,我们可以看下第二次调用时的url路径:
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请求:
请求成功!
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
猜你喜欢
- 一、什么是ASMASM是一个java字节码操纵框架,它能被用来动态生成类或者增强既有类的功能。ASM 可以直接产生二进制 class 文件,
- Java 数据库连接池详解数据库连接池的原理是:连接池基本的思想是在系统初始化的时候,将数据库连接作为对象存储在内存中,当用户需要访问数据库
- spring validation多层对象校验1、第一层对象定义import java.io.Serializable; import ja
- 在该示例中,阐述了SpringMVC如何上传文件。1、上传页面upload.jsp<body> <form
- 一、Thread 类了解如何使用Thread 类实现多线程之后,继续学习Thread 类实现多线程之后的相关功能及方法。1、操作线程名称的方
- 知识点:1.使用SQL Helper创建数据库2.数据的增删查改(PRDU:Put、Read、Delete、Update)背景知识:上篇文章
- java和javascript长得是如此地相像,那么它们是一回事儿吗?现在让我来揭晓答案吧!JavaScript 是一种嵌入式脚本文件,直接
- 注入集合(数组、List、Map、Set)类型属性(1)创建类,定义数组,list,map,set类型属性,并且生成对应的set方法。(2)
- logback输出日志屏蔽quartz的debug等级日志在一个spring的老项目中,使用了logback来作为日志管理,logback.
- Java并发包的locks包里的锁基本上已经介绍得差不多了,ReentrantLock重入锁是个关键,在清楚的了解了同步器AQS的运行机制后
- 一、基本定义Arrays类,全路径java.util.Arrays,主要功能为操作数组,Arrays类的所有方法均为静态方法,所以调用方式全
- 1. 每个编译单元(文件)都只能有一个public类。即每个编译单元都有单一的公共接口,用public类实现。此时,mian()就必须要包含
- 目的官方的Drools范例大都是基于纯Java项目或Maven项目,而基于Spring Boot项目的很少。本文介绍如何在Spring Bo
- 一、目的本篇文章的目的是记录本人使用flutter加载与调用第三方aar包。二、背景本人go后端,业余时间喜欢玩玩flutter。一直有一个
- 问题背景通常我们开发的时候,需要联合控制台和Navicat/PLSQL等工具进行语句的拼接检查,如果只是输出了一堆???,那么将极大降低我们
- 前沿知识ThreadLocal存储线程变量,使用set方法设置变量,使用get方法获取变量线程隔离的实现是每个Thread类有一个类型为Th
- 本文介绍Android中的5种数据存储方式。数据存储在开发中是使用最频繁的,在这里主要介绍Android平台中实现数据存储的5种方式,分别是
- Java float和double精度范围大小要想理解float和double的取值范围和计算精度,必须先了解小数是如何在计算机中存储的:举
- 背景:在Android中按照数据保存的方式,可以分为如下几种Content Provider (用的SQLite实现),SQLite,Sha
- 前言在项目中,如果我们要遵循分层领域模型规约: 话,肯定避免不了在DTO、VO、BO、AO、VO、Query等实体的转换,我们通常有几种做法