我们经常有这样一个场景,比如:在springboot拦截器中想截取post请求的body参数做一些中间处理,或者用到自定义注解,想拦截一些特定post请求的方法的参数,记录一些请求日志。

想到了使用拦截器来实现这个功能

  当请求来到过滤器时,会有一个Request参数,通过该参数就能获取到请求路径和请求参数,以及相关内容,但是getParameterMap()方法只能够获取到GET请求的参数,如果是POST方法传的JSON那就没法获取到,那如何获取呢,POST的请求是在请求体body中,而POST请求中的body参数是已流形式存在的

所以我们可以通过获取到输入流来获取body

val inputStream: ServletInputStream = httpRequest.getInputStream()
val reader = InputStreamReader(inputStream, StandardCharsets.UTF_8)
val bfReader = BufferedReader(reader)
val sb = StringBuilder()
var line: String?
while (bfReader.readLine().also { line = it } != null) {
sb.append(line)
}
println(sb.toString())

  通过上面的方法,我们确实能在过滤器中获取到POST的JSON参数了,但是按照上面的方法实现的过滤器,我们会发现,当请求经过过滤器来到Controller的时候,请求参数不见了



可以看到,过滤器确实拿到JSON参数,但是接着报了一个request body missing的异常,也就是请求来到Controller时,参数没有了,这是为啥呢?

从源码分析我们可以看到

  SpringBoot也是通过获取request的输入流来获取参数,这样上面的疑问就能解开了,为什么经过过滤器来到Controller请求参数就没了,这是因为 InputStream read方法内部有一个,postion,标志当前流读取到的位置,每读取一次,位置就会移动一次,如果读到最后,InputStream.read方法会返回-1,标志已经读取完了,如果想再次读取,可以调用inputstream.reset方法,position就会移动到上次调用mark的位置,mark默认是0,所以就能从头再读了。但是呢 是否能reset又是由markSupported决定的,为true能reset,为false就不能reset,从源码可以看到,markSupported是为false的,而且一调用reset就是直接异常

  所以这也就代表,InputStream只能被读取一次,后面就读取不到了。因此我们在过滤器的时候,已经将InputStream读取过了一次,当来到Controller,SpringBoot读取InputStream的时候自然是什么都读取不到了

  既然InputStream只能读取一次,那我们可以把InputStream给保存下来,然后完整的传下去SpringBoot就可以读取到了,这里就需要用到HttpServletRequest的包装类HttpServletRequestWrapper了,该类可以自定义一些方法

自定义 HttpServletRequestWrapper

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

package com.qianxin.scm.interceptor

import org.springframework.util.StreamUtils
import java.io.BufferedReader
import java.io.ByteArrayInputStream
import java.io.IOException
import java.io.InputStreamReader
import java.nio.charset.StandardCharsets
import javax.servlet.ReadListener
import javax.servlet.ServletInputStream
import javax.servlet.http.HttpServletRequest
import javax.servlet.http.HttpServletRequestWrapper class RequestWrapper(request: HttpServletRequest) : HttpServletRequestWrapper(request) { private var body: ByteArray = StreamUtils.copyToByteArray(request.inputStream) //转换成String
fun getBodyString(): String? {
return String(body, StandardCharsets.UTF_8)
} @Throws(IOException::class)
override fun getReader(): BufferedReader? {
return BufferedReader(InputStreamReader(inputStream))
} //把保存好的InputStream,传下去
@Throws(IOException::class)
override fun getInputStream(): ServletInputStream? {
val bais = ByteArrayInputStream(body)
return object : ServletInputStream() {
@Throws(IOException::class)
override fun read(): Int {
return bais.read()
} override fun isFinished(): Boolean {
return false
} override fun isReady(): Boolean {
return false
} override fun setReadListener(readListener: ReadListener?) {}
}
}
}

然后定义一个 DispatcherServlet子类来分派 上面自定义的 RequestWrapper,这里需要特别注意,在这个派发类处理派发request的自定义包装类的时候,如果是普通的post请求不会有问题,但是如果一旦是上传文件类型之类的post请求,则这个request必须用StandardServletMultipartResolver类的方法包装,所以这里做了一个请求类型的判断,很多文章都没有考虑这种情况,会导致如果项目中有上传文件的接口,都会不可用,所以这里需要特别注意!注意!注意!

package com.qianxin.scm.interceptor

import org.springframework.web.multipart.support.StandardServletMultipartResolver
import org.springframework.web.servlet.DispatcherServlet
import javax.servlet.http.HttpServletRequest
import javax.servlet.http.HttpServletResponse class PostReqeustDispatcherServlet : DispatcherServlet() {
override fun doDispatch(req: HttpServletRequest, resp: HttpServletResponse) {
var request: HttpServletRequest = req
val contentType: String? = req.contentType
val method = "multipart/form-data"
//如果是文件类型上传,则需要用这个request
if (contentType != null && contentType.contains(method)) {
// 将转化后的 request 放入过滤链中
request = StandardServletMultipartResolver().resolveMultipart(request)
}
val requestWrapper = RequestWrapper(request)
super.doDispatch(requestWrapper, resp) }
}

然后配置一下:

package com.qianxin.scm.interceptor

import org.springframework.beans.factory.annotation.Qualifier
import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.web.servlet.DispatcherServlet
import org.springframework.web.servlet.config.annotation.InterceptorRegistry
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer @Configuration
class WebConfig : WebMvcConfigurer { @Bean
fun getLoginInterceptor(): LoginInterceptor? {
return LoginInterceptor()
} override fun addInterceptors(registry: InterceptorRegistry) { registry.addInterceptor(getLoginInterceptor()!!)
.addPathPatterns("/**")
.excludePathPatterns("/static/**")
.excludePathPatterns("/webjars/**")
.excludePathPatterns("/swagger-ui/**")
} @Bean
@Qualifier(DispatcherServletAutoConfiguration.DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
fun dispatcherServlet(): DispatcherServlet? {
return PostReqeustDispatcherServlet()
}
}

拦截器中读取post的body参数,可以用如下方法:

 val requestWrapper = RequestWrapper(request)
val postData = requestWrapper.getBodyString()

总结一下

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

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

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

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

springboot如何在拦截器中拦截post请求参数以及解决文件类型上传问题的更多相关文章

  1. Struts2 在登录拦截器中对ajax请求的处理

    前言: 由于ajax请求不像http请求,可以直接进行页面跳转,你返回的所有东西,ajax都只会识别为一个字符串. 之前尝试的方法是在拦截器中返回一个标识给ajax,然后再在每一个ajax请求成功之后 ...

  2. struts2 文件的上传下载 表单的重复提交 自定义拦截器

    文件上传中表单的准备 要想使用 HTML 表单上传一个或多个文件 须把 HTML 表单的 enctype 属性设置为 multipart/form-data 须把 HTML 表单的method 属性设 ...

  3. SpringBoot拦截器中Bean无法注入(转)

    问题 这两天遇到SpringBoot拦截器中Bean无法注入问题.下面介绍我的思考过程和解决过程: 1.由于其他bean在service,controller层注入一点问题也没有,开始根本没意识到Be ...

  4. SpringBoot拦截器中service或者redis注入为空的问题

    原文:https://my.oschina.net/u/1790105/blog/1490098 这两天遇到SpringBoot拦截器中Bean无法注入问题.下面介绍我的思考过程和解决过程: 1.由于 ...

  5. 【跨域】SpringBoot跨域,拦截器中,第一次获取的请求头为NULL,发送两次请求的处理方式

    背景: 在做前后端分离时,牵扯到跨域,但是已经设置了跨域 前端设置了允许携带Cookie axios.defaults.withCredentials = true; 后端也配置了跨域 浏览器端查看发 ...

  6. struts文件上传拦截器中参数的配置(maximumSize,allowedTypes ,allowedExtensions)问题

    <interceptor-ref name="fileUpload"> <param name="allowedTypes">image ...

  7. spring boot拦截器中获取request post请求中的参数

    最近有一个需要从拦截器中获取post请求的参数的需求,这里记录一下处理过程中出现的问题. 首先想到的就是request.getParameter(String )方法,但是这个方法只能在get请求中取 ...

  8. SpringBoot拦截器中无法注入bean的解决方法

    SpringBoot拦截器中无法注入bean的解决方法 在使用springboot的拦截器时,有时候希望在拦截器中注入bean方便使用 但是如果直接注入会发现无法注入而报空指针异常 解决方法: 在注册 ...

  9. 解决 Springboot中Interceptor拦截器中依赖注入失败

    问题: 在Springboot拦截器Interceptor中使用@Resource依赖注入时,发现运行的时候被注解的对象居然是null,没被注入进去 原配置为: @Configurationpubli ...

  10. 【SpringBoot】拦截器使用@Autowired注入接口为null解决方法

    最近使用SpringBoot的自定义拦截器,在拦截器中注入了一个DAO,准备下面作相应操作,拦截器代码: public class TokenInterceptor implements Handle ...

随机推荐

  1. 19、从键盘输入两个数字n,m,求解n,m的最小公倍数

    /* 从键盘输入两个数字n,m,求解n,m的最小公倍数 */ #include <stdio.h> #include <stdlib.h> void getLowsetComM ...

  2. day18-web工程路径

    web工程路径 配置tomcat运行快捷键 tomcat启动的默认快捷键时shift+f10,可以自定义配置:file-setting-keymap-搜索run,找到右边写有shift+f10的选项, ...

  3. qtCreator警告解决

    警告 qtCreator Warning: QT_DEVICE_PIXEL_RATIO is deprecated. Instead use: QT_AUTO_SCREEN_SCALE_FACTOR ...

  4. Asp.Net Core&Jaeger实现链路追踪

    前言 随着应用愈发复杂,请求的链路也愈发复杂,微服务化下,更是使得不同的服务分布在不同的机器,地域,语言也不尽相同.因此需要借助工具帮助分析,跟踪,定位请求中出现的若干问题,以此来保障服务治理,链路追 ...

  5. 5:Echarts数据可视化-多条曲线、多个子图、TreeMap类似盒图、树形图、热力图、词云

    〇.目标 本次实验主要基于Echarts的Python库实现高维数据.网络和层次化数据.时空数据和文本数据的可视化,掌握可视化的操作流程和相关库的使用. 一.绘制平行坐标系 平行坐标是信息可视化的一种 ...

  6. 【java】【File】用File相关类写一个小工具,完成从指定目录下抽取指定文件并复制到新路径下完成重命名的功能

    今日份代码: import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import java.io.*; i ...

  7. 这玩意也太猛了!朋友们,我在此严正呼吁大家:端好饭碗,谨防 AI!

    你好呀,我是歪歪. 最近几天大火的 ChatGPT 你玩了吗? 如果你不知道它是个什么东西,那么我让它给你来个自我介绍: 说白了,就是一个可以对话的人工智能. 我开始以为就是一个升级版的"小 ...

  8. cs231n__4.2 神经网络 Neural networks

    CS231n 学习笔记 4.2 神经网络 Neural networks 之前我们已经使用了 很多线性分类函数 现在我们不用单变换的: 我们首先有线性层,然后有这个非线性计算,继而在顶层再加入另一个线 ...

  9. 一文掌握MyBatis的动态SQL使用与原理

    摘要:使用动态 SQL 并非一件易事,但借助可用于任何 SQL 映射语句中的强大的动态 SQL 语言,MyBatis 显著地提升了这一特性的易用性. 本文分享自华为云社区<MyBatis详解 - ...

  10. JavaScript:操作符:算术运算符(加减乘除模幂)及其隐式转换数据类型

    加法+ 减法- 乘法* 除法/ 模运算% 幂运算**,即a ** b求的是a的b次方 执行上述运算时,当两个操作数有非数字时,JS会隐式转换为数字,再进行运算: 一些特殊的非数字,会进行如下转换: t ...