SpringBoot 拦截器获取http请求参数
SpringBoot 拦截器获取http请求参数—— 所有骚操作基础
获取http请求参数是一种刚需
我想有的小伙伴肯定有过获取http请求的需要,比如想
- 前置获取参数,统计请求数据
- 做服务的接口签名校验
- 敏感接口监控日志
- 敏感接口防重复提交
等等各式各样的场景,这时你就需要获取 HTTP 请求的参数或者请求body,一般思路有两种,一种就是自定义个AOP去拦截目标方法,第二种就是使用拦截器。整体比较来说,使用拦截器更灵活些,因为每个接口的请求参数定义不同,使用AOP很难细粒度的获取到变量参数,本文主线是采用拦截器来获取HTTP请求。
定义拦截器获取请求
基于 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));同时也发现拦截器竟然走了两遍,这又是咋回事呢?

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

ServletInputStream(CoyoteInputStream) 输入流无法重复调用
而之前出现的 Required request body is missing 错误 其实是ServletInputStream被读取后无法第二次再读取了,所以我们要把读取过的内容存下来,然后需要的时候对外提供可被重复读取的ByteArrayInputStream。
对于MVC的过滤器来说,我们就需要重写 ServletInputStream 的 getInputStream()方法。
自定义 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请求:

请求成功!
总结一下 展望一下
如果你想对HTTP请求做些骚操作,那么前置获取HTTP请求参数是前提,为此文本给出了使用MVC拦截器获取参数的样例。
在获取HTTP Body 的时候,出现了 Required request body is missing 的错误,同时拦截器还出现执行了两遍的问题,这是因为 ServletInputStream被读取了两遍导致的,tomcat截取到异常后就转发到 /error 页面 被拦截器拦截到了,拦截器也就执行了两遍。
为此我们通过自定义 HttpServletRequestWrapper 来包装一个可被重读读取的输入流,来达到期望的拦截效果。
在获取到HTTP的请求参数后,我们可以前置做很多操作,比如常用的服务端接口签名验证,敏感接口防重复请求等等。
个人水平有限,如果文章有逻辑错误或表述问题还请指出,欢迎一起交流。
SpringBoot 拦截器获取http请求参数的更多相关文章
- Structs2 中拦截器获取请求参数
前言 环境:window 10,JDK 1.7,Tomcat 7 测试代码 package com.szxy.interceptor; import java.util.Map; import jav ...
- 实现Feign请求拦截器,对请求header等参数进行转发
参考:Feign传递请求头信息(Finchley版本) 问题:通过Feign远程调用服务,无法传递header参数. 解决方式:实现RequestInterceptor接口(对所有的Feign请求进行 ...
- SpringBoot获取http请求参数的方法
SpringBoot获取http请求参数的方法 原文:https://www.cnblogs.com/zhanglijun/p/9403483.html 有七种Java后台获取前端传来参数的方法,稍微 ...
- Springboot拦截器实现IP黑名单
Springboot拦截器实现IP黑名单 一·业务场景和需要实现的功能 以redis作为IP存储地址实现. 业务场景:针对秒杀活动或者常规电商业务场景等,防止恶意脚本不停的刷接口. 实现功能:写一个拦 ...
- 【spring源码学习】springMVC之映射,拦截器解析,请求数据注入解析,DispatcherServlet执行过程
[一]springMVC之url和bean映射原理和源码解析 映射基本过程 (1)springMVC配置映射,需要在xml配置文件中配置<mvc:annotation-driven > ...
- springboot + 拦截器 + 注解 实现自定义权限验证
springboot + 拦截器 + 注解 实现自定义权限验证最近用到一种前端模板技术:jtwig,在权限控制上没有用springSecurity.因此用拦截器和注解结合实现了权限控制. 1.1 定义 ...
- 学习axios必知必会(2)~axios基本使用、使用axios前必知细节、axios和实例对象区别、拦截器、取消请求
一.axios的基本使用: ✿ 使用axios前必知细节: 1.axios 函数对象(可以作为axios(config)函数使用去发送请求,也可以作为对象调用方法axios.request(confi ...
- Java结合SpringBoot拦截器实现简单的登录认证模块
Java结合SpringBoot拦截器实现简单的登录认证模块 之前在做项目时需要实现一个简单的登录认证的功能,就寻思着使用Spring Boot的拦截器来实现,在此记录一下我的整个实现过程,源码见文章 ...
- SpringBoot拦截器中Bean无法注入(转)
问题 这两天遇到SpringBoot拦截器中Bean无法注入问题.下面介绍我的思考过程和解决过程: 1.由于其他bean在service,controller层注入一点问题也没有,开始根本没意识到Be ...
随机推荐
- iptables看门狗
近来业内很多服务器因redis造成服务器被黑,这个攻击的防范重点在于防火墙!! 有时为了方便我们可能会将iptables临时关闭,方便完倘若忘记把它打开,黑客大摇大摆就走进来. 这时候,我们需要条看门 ...
- 内网 Maven 编译
内网 Maven 编译 有个特殊的需求,在不联网的情况下编译 Java 项目. 想到两种方案: 搭建 Nexus 私有镜像仓库. 直接把依赖 jar包 放在编译机的 maven 本地库中. 步骤简述 ...
- 防止多次引入js文件导致的重复注册点击事件
前端代码中的js文件如果是动态引入的或者是某个事件操作进行注册的,那么重复的引入js文件或者多次触发注册事件会导致事件多次进行注册,造成不必要的麻烦,所以需要在每次注册之前将先前的事件进行取消,下面以 ...
- 使用halo搭建自己的博客并配置https域名访问
首先进行java配置 # 1. 下载jdk [下载地址](https://www.oracle.com/cn/java/technologies/javase-downloads.html) - 一定 ...
- mycat数据库集群系列之数据库多实例安装
mycat数据库集群系列之数据库多实例安装 最近在梳理数据库集群的相关操作,现在花点时间整理一下关于mysql数据库集群的操作总结,恰好你又在看这一块,供一份参考.本次系列终结大概包括以下内容:多数据 ...
- 【luogu1613】跑路 - 倍增+Floyd
题目描述 小A的工作不仅繁琐,更有苛刻的规定,要求小A每天早上在6:00之前到达公司,否则这个月工资清零.可是小A偏偏又有赖床的坏毛病.于是为了保住自己的工资,小A买了一个十分牛B的空间跑路器,每秒钟 ...
- JavaScript设计模式之策略模式【组合委托】
前言:语言只是工具,思想才是核心 今天要总结的是 策略模式 策略在开发中的应用非常广泛,所以也是非常常见且使用的设计模式. 在实际开发中,往往在实现一个功能时,有多种解决方案可行. 常见场景: 解压: ...
- MySQL · 性能优化 · MySQL常见SQL错误用法(转自-阿里云云栖社区)
作者:阿里云云栖社区链接:https://zhuanlan.zhihu.com/p/26043916来源:知乎著作权归作者所有,转载请联系作者获得授权. 前言 MySQL在2016年仍然保持强劲的数据 ...
- openvswitch 流表操作
流表组成 每条流表规则由一些列字段组成,可以分为**基础字段.匹配字段和动作字段**三部分. 在打印流表时,在流表中还存在一些显示字段,如duration,idle_age等,此处把这些字段也暂时归之 ...
- 手把手教Linux驱动2-之模块参数和符号导出
通过<手把手教Linux驱动1-模块化编程,玩转module>的学习,我们已经掌握了如何向内核加载一个模块,现在我们学习模块之间如何传递参数. 一.给模块传递参数 当我们加载一个模块到Li ...