一、事由
由于我们业务接口需要做签名校验,但因为是老系统了签名规则被放在了Body里而不是Header里面,但是我们不能在每个Controller层都手动去做签名校验,这样不是优雅的做法,然后我就写了一个AOP,在AOP中实现签名校验,之后Controller层就报以下错误:
java.lang.IllegalStateException: getReader() has already been called for this request
at org.apache.catalina.connector.Request.getInputStream(Request.java:1074)
at org.apache.catalina.connector.RequestFacade.getInputStream(RequestFacade.java:365)
at javax.servlet.ServletRequestWrapper.getInputStream(ServletRequestWrapper.java:151)
at javax.servlet.ServletRequestWrapper.getInputStream(ServletRequestWrapper.java:151)
at javax.servlet.ServletRequestWrapper.getInputStream(ServletRequestWrapper.java:151)
二、分析源码
它告诉我们,getReader
方法在这次请求中已经被调用了,可是为什么getReader已经被调用了呢,我们来分析一下源码,只有看懂了源码我们才能理解为什么,同时也为自己积累源码Debug的经验,这是我的Controller层代码(这里只是做了个实例读取流数据):
@RequestMapping("/2")
public String index2(HttpServletRequest request) throws IOException {
return "请使用前端页面进行访问 " + request.getInputStream().read();
}
但是usingReader为什么会为true呢,我们点击看一下这个变量赋值的地方:
它一共有两个赋值的地方,第一个赋值为fase我们可以直接忽略,看一下true那个
三、解决办法
我看了这个getReader方法调用地方就是我在AOP中读取流中数据做签名校验的时候取的,那这咋办?AOP做了一层数据读取就导致Controller拿不到数据了,想了又想,我想到一个解决办法:
使用HttpServletRequest的setAttribute方法。
这个方法用于在HTTP请求直接共享数据,并且在请求完成之后自动销毁,完全不用担心生命周期问题,用它再适合不过了,我们可以写个请求过滤器,过滤所有的请求,将body数据设置到请求域当中:
/**
* 用于处理读取多次请求体内容
* 读取Token一次,解析数据一次
*
* Created By XuanRan
*/
@Component
@Order(Ordered.LOWEST_PRECEDENCE)
public class MultipleReadHttpRequestFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
StringBuilder sb = new StringBuilder();
BufferedReader reader = request.getReader();
String line;
while ((line = reader.readLine()) != null) {
sb.append(line);
}
request.setAttribute("CACHED_REQUEST_BODY", sb.toString());
filterChain.doFilter(request, response);
}
}
然后我这里使用的是SpringSecurity,我们把它添加到Filter链当中
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
// 注解标记允许匿名访问的url
ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry registry = httpSecurity.authorizeRequests();
permitAllUrl.getUrls().forEach(url -> registry.antMatchers(url).permitAll());
// 中间省略了一部分.....
// 中间省略了一部分.....
// 中间省略了一部分.....
// 中间省略了一部分.....
httpSecurity.addFilterBefore(corsFilter, LogoutFilter.class);
// 添加请求体读取
httpSecurity.addFilterAfter(multipleReadHttpRequestFilter, CorsFilter.class);
}
然后我们就可以在Controller的HttpServletRequest的getAttribute根据CACHED_REQUEST_BODY这个key取对应的数据了,我这里为了方便,进一步写了一个AOP,自动的将数据完成转换:
然后就可以在Controller层直接拿到处理后的对象了
版权所有:XuanRan
未经书面授权,禁止转载