过滤器和拦截器的区别
在Spring Boot中,过滤器(Filter)和拦截器(Interceptor)都是处理HTTP请求的重要组成部分。尽管他们在功能上有些许相似,但他们的使用场景和实现方式有所不同。下面是这两者之间的主要区别:
-
所在层次:过滤器是Servlet规范中的一部分,由Servlet容器管理。拦截器是Spring MVC的一部分,由DispatcherServlet管理。这意味着过滤器在拦截器之前执行。
-
拦截范围:过滤器可以拦截所有的请求,包括静态资源,如HTML,JS,CSS等。而拦截器只拦截那些被Spring的DispatcherServlet处理的请求。
-
参数传递:过滤器只能使用ServletRequest和ServletResponse对象。而拦截器可以访问到更具体的请求和响应对象(例如,HttpServletRequest和HttpServletResponse),还可以通过HandlerMethod获取处理请求的方法和类的信息。
-
功能:过滤器主要用于处理通用的请求处理,例如:设置请求编码,过滤非法字符,防止SQL注入等。拦截器主要用于处理业务逻辑,例如:登录检查,权限验证,程序执行时间等。
-
底层实现:过滤器的实现基于回调函数。而拦截器(代理模式)的实现基于反射
引入依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.16</version>
</dependency>
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
<version>2.0.27</version>
</dependency>
</dependencies>
一、解决请求体不能反复读取并编写拦截器记录日志
自定义wrapper复制请求流, 如果不重写会报 java.lang.IllegalStateException: getInputStream() has already been called for this request 异常, 原因是request请求流不能重复读取。
import lombok.Getter;
import org.springframework.util.StreamUtils;
import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
public class MyHttpServletRequestWrapper extends HttpServletRequestWrapper {
private final byte[] body;
/** 复制请求body */
@Getter
private final String bodyString;
public MyHttpServletRequestWrapper (HttpServletRequest request) throws IOException {
super(request);
this.bodyString = StreamUtils.copyToString(request.getInputStream(), StandardCharsets.UTF_8);
this.body = this.bodyString.getBytes(StandardCharsets.UTF_8);
}
@Override
public ServletInputStream getInputStream() throws IOException {
//返回body的流信息即可
try(final ByteArrayInputStream bais = new ByteArrayInputStream(this.body)){
return new ServletInputStream() {
@Override
public boolean isFinished() {
return false;
}
@Override
public boolean isReady() {
return false;
}
@Override
public void setReadListener(ReadListener readListener) {
}
@Override
public int read() {
return bais.read();
}
};
}
}
@Override
public BufferedReader getReader() throws IOException{
return new BufferedReader(new InputStreamReader(this.getInputStream()));
}
}
自定义filter, 必须将自定义的wrapper通过过滤器传下去, 不传不会调用重写后的getInputStream()和getReader()方法
import com.example.filter.demo.wrapper.MyHttpServletRequestWrapper;
import org.apache.commons.lang3.StringUtils;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.Objects;
import java.util.Optional;
@Component
@WebFilter(filterName = "RewriteRequestFilter", urlPatterns = "/*")
@Order(0)
public class RewriteRequestFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
//文件上传类型 不需要处理,否则会报java.nio.charset.MalformedInputException: Input length = 1异常
if (Objects.isNull(request) || Optional.ofNullable(request.getContentType()).orElse(StringUtils.EMPTY).startsWith("multipart/")) {
chain.doFilter(request, response);
return;
}
//自定义wrapper 处理流,必须在过滤器中处理,然后通过FilterChain传下去, 否则重写后的getInputStream()方法不会被调用
MyHttpServletRequestWrapper requestWrapper = new MyHttpServletRequestWrapper((HttpServletRequest)request);
chain.doFilter(requestWrapper,response);
}
}
自定义日志类
import lombok.Data;
import java.util.Map;
@Data
public class LoggerInfoDTO {
/**
*
*/
private String clientIp;
/**
*
*/
private String url;
/**
*
*/
private String type;
/**
* 请求方式GET、POST、PUT、DELETE
*/
private String method;
/**
* URL后的参数
*/
private Map<String, String[]> requestParam;
/**
* restful风格的路径参数
*/
private Map<String, Object> pathVariable;
/**
* 请求体信息
*/
private String requestBody;
private String sessionId;
}
自定义interceptor拦截器, 判断如果是自己的wrapper, 从wrapper中获取请求参数。只能在拦截器中获取请求参数,
1.如果在自定义的Filter中获取请求参数, restful风格的请求参数无法获取。
2.如果在AOP中获取请求参数, 获取的是序列化后的请求参数(基本类型默认值也会被获取)
import cn.hutool.extra.servlet.ServletUtil;
import cn.hutool.json.JSONUtil;
import com.example.filter.demo.dto.LoggerInfoDTO;
import com.example.filter.demo.wrapper.MyHttpServletRequestWrapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.HandlerMapping;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Objects;
@Slf4j
public class LoggerInterceptor implements HandlerInterceptor {
private static final ThreadLocal<LoggerInfoDTO> threadLocal = new ThreadLocal<>();
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
LoggerInfoDTO loggerInfo = new LoggerInfoDTO();
// 设置请求方法,GET,POST...
loggerInfo.setMethod(request.getMethod());
// 设置请求sessionId
loggerInfo.setSessionId(request.getRequestedSessionId());
// 设置请求路径
loggerInfo.setUrl(request.getRequestURI());
// 设置@RequestParam注解的参数
loggerInfo.setRequestParam(request.getParameterMap());
// 设置restful请求参数,必须在interceptor拦截其中才能这样获取到restful参数
Object attribute = request.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE);
if(Objects.nonNull(attribute)){
loggerInfo.setPathVariable(JSONUtil.parseObj(attribute));
}
//设置客户端ip
loggerInfo.setClientIp(ServletUtil.getClientIP(request));
//如果是自定义wrapper, 从自定义wrapper中获取请求参数, 必须在interceptor拦截器中处理, 否则restful风格的请求参数获取不到。
if(request instanceof MyHttpServletRequestWrapper){
MyHttpServletRequestWrapper myHttpServletRequestWrapper = (MyHttpServletRequestWrapper) request;
String bodyString = myHttpServletRequestWrapper.getBodyString();
if(StringUtils.isNoneBlank(bodyString)){
loggerInfo.setRequestBody(bodyString);
}
}
//放到ThreadLocal中, 这里可以根据自己的项目业务处理
log.info("loggerInfo : {}", JSONUtil.toJsonPrettyStr(loggerInfo));
threadLocal.set(loggerInfo);
return true;
}
}
编写拦截器配置类
import com.example.filter.demo.interceptor.LoggerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(loggerInterceptor())
// 拦截规则 ,拦截那些路径
.addPathPatterns("/**")
// 那些路径不拦截
.excludePathPatterns("/user/login","/error");
}
@Bean
public LoggerInterceptor loggerInterceptor() {
return new LoggerInterceptor();
}
}
二、编写防止xss攻击的过滤器
增强MyHttpServletRequestWrapper
/**
* @param request
*/
public XssHttpServletRequestWrapper(HttpServletRequest request) throws IOException {
super(request);
String json = StreamUtils.copyToString(request.getInputStream(), StandardCharsets.UTF_8);
JSONObject jsonObject = JSONUtil.parseObj(json);
// xss过滤,HtmlUtil.filter的validateEntities方法会将双引号转码,所以不能将json整个过滤
jsonObject.forEach((key,value) -> {
value = HtmlUtil.filter(String.valueOf(value)).trim();
jsonObject.set(key, value);
});
this.bodyString = JSONUtil.toJsonStr(jsonObject);
this.body = this.bodyString.getBytes(StandardCharsets.UTF_8);
}
/**
* 重写获取参数进行转义
* @param name
* @return
*/
@Override
public String[] getParameterValues(String name) {
String[] values = super.getParameterValues(name);
if (values != null) {
int length = values.length;
String[] escapesValues = new String[length];
for (int i = 0; i < length; i++) {
// 防xss攻击和过滤前后空格
escapesValues[i] = HtmlUtil.filter(values[i]).trim();
}
return escapesValues;
}
return super.getParameterValues(name);
}
三、使用拦截器+注解进行鉴权
编写注解
import java.lang.annotation.*;
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface AuthCheck {
/**
* 授权编码
* @return
*/
String[] authCode();
}
import java.lang.annotation.*;
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface NoAuth {
}
编写拦截器
import com.wulei.interceptor.demo.annotation.AuthCheck;
import com.wulei.interceptor.demo.annotation.NoAuth;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
@Component
public class AuthCheckInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if (!(handler instanceof HandlerMethod)) {
return true;
}
// HandlerMethod 提供对方法参数、方法返回值、方法注释等的便捷访问。
HandlerMethod handlerMethod = (HandlerMethod) handler;
// Method 提供有关类或接口上的单个方法的信息和访问。
Method method = handlerMethod.getMethod();
if (method.getAnnotation(NoAuth.class) != null // 获取方法上注解
// 获取类上注解
|| handlerMethod.getBeanType().getAnnotation(NoAuth.class) != null) {
return true;
}
// TODO 获取用户角色和权限功能点
// 判断注解功能点是否正确
// 获取方法上注解
AuthCheck authCheck = method.getAnnotation(AuthCheck.class);
if (authCheck == null) {
// 获取类上注解
authCheck = handlerMethod.getBeanType().getAnnotation(AuthCheck.class);
}
String[] authCode = authCheck != null ? authCheck.authCode() : null;
if (authCode == null || authCode.length == 0) {
return false;
}
// TODO 判断用户功能点权限
return true;
}
}