1. 场景
自定义Token后,需要在拦截器中进行token验证。在验证的过程中需要读取HttpServletRequest的body部分数据进行验证。
2. 存在问题
如果直接配置拦截器进行urlPatterns拦截,并进行参数验证,在拦截器中获取request的输入流,会导致controller层因为无法获取到request相应参数而抛出异常,从而拦截器再次拦截/error请求。
3.解决办法
在拦截器执行之前,进行过滤器配置,在过滤器中对Request进行包装,HttpServletRequestWrapper 是 httpServletRequest 的包装类可以很好的解决这个问题。
3.1 配置拦截器,实现HandlerInterceptor
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.MediaType;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Slf4j
public class BaseInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String token = request.getHeader("token") != null
? (String) request.getHeader("token") : "";
if(StringUtils.isNullOrEmpty(token)){
return false;
}
log.info("[preHandle] executing... request uri is {}", request.getRequestURI());
if(isJson(request)){
String jsonParam = ((BodyReaderHttpServletRequestWrapper)request).getBodyStr();
//业务逻辑
}else{
log.info("参数不为JSOn格式");
return false;
}
}
private boolean isJson(HttpServletRequest request) {
if (request.getContentType() != null) {
return request.getContentType().equals(MediaType.APPLICATION_JSON_VALUE) ||
request.getContentType().equals(MediaType.APPLICATION_JSON_UTF8_VALUE);
}
return false;
}
}
3.2 注册拦截器
addPathPatterns(“/**”)代表拦截全部层级uri,patterns是不拦截的请求uri。
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import java.util.*;
@Configuration
public class InterceptorConfigurer implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
BaseInterceptor baseInterceptor = new BaseInterceptor();
List<String> patterns = new ArrayList<>();
patterns.add("/helloworld");
registry.addInterceptor(baseInterceptor).addPathPatterns("/**").excludePathPatterns(patterns);
}
}
3.2 配置HttpServletRequestWrapper
import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.IOException;
import java.io.*;
import java.nio.charset.Charset;
public class BodyReaderHttpServletRequestWrapper extends HttpServletRequestWrapper {
private final byte[] body;
private String bodyStr;
public BodyReaderHttpServletRequestWrapper(HttpServletRequest request) throws IOException {
super(request);
String bodyString = getBodyString(request);
body = bodyString.getBytes(Charset.forName("UTF-8"));
bodyStr=bodyString;
}
public String getBodyStr() {
return bodyStr;
}
@Override
public ServletInputStream getInputStream() throws IOException {
final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(body);
return new ServletInputStream() {
@Override
public int read() throws IOException {
return byteArrayInputStream.read();
}
@Override
public boolean isFinished() {
return false;
}
@Override
public boolean isReady() {
return false;
}
@Override
public void setReadListener(ReadListener readListener) {
}
};
}
public String getBodyString(HttpServletRequest request) throws IOException {
StringBuilder sb = new StringBuilder();
InputStream inputStream = null;
BufferedReader reader = null;
try {
inputStream = request.getInputStream();
reader = new BufferedReader(
new InputStreamReader(inputStream, Charset.forName("UTF-8")));
char[] bodyCharBuffer = new char[1024];
int len = 0;
while ((len = reader.read(bodyCharBuffer)) != -1) {
sb.append(new String(bodyCharBuffer, 0, len));
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return sb.toString();
}
}
3.3 配置过滤器
注意:这里只是将POST请求的request进行包装,具体实现根据业务需求进行更改。
import org.springframework.context.annotation.ComponentScan;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;
import javax.servlet.http.HttpServletRequest;
@ComponentScan
@WebFilter(filterName = "httpServletRequestWrapperFilter", urlPatterns = {"/*"})
public class HttpServletRequestWrapperFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
System.out.println("doFilter");
ServletRequest requestWrapper = null;
if (request instanceof HttpServletRequest) {
HttpServletRequest httpRequest = (HttpServletRequest) request;
//遇到post方法才对request进行包装
String methodType = httpRequest.getMethod();
if ("POST".equals(methodType)) {
requestWrapper = new BodyReaderHttpServletRequestWrapper(
(HttpServletRequest) request);
}
}
if (null == requestWrapper) {
chain.doFilter(request, response);
} else {
chain.doFilter(requestWrapper, response);
}
}
@Override
public void destroy() {
}
}
4 补充
- 配置的过滤器一定要能被SpringBootApplication,即启动类扫描到,可以在启动类上加@ServletComponentScan,为了规范也可以在*Filter类上加@ComponentScan注解。
- 解释为什么拦截器中读取了body数据,controller不能再次读取
我们在读取body数据中,是从流中进行读取,全部或部分数据存储在内存中。
以InputStream为例,在读取过程中,会记录当前读取到的位置(mark position),第二次读取是从标志位置继续读取,当然也可以使用reset()方法重置(mark position)。
但是ServletInputStream 中不能调用reset方法,也就是你第一次读取如果将body数据全部加载,第二次读取的数据将会为空。
- AOP方法
拦截器中读取body数据也可以使用aop方法进行读取,具体参考以下文章。
aop读取body数据