Filter,过滤器,用于对访问web服务器管理的所有web资源(例如Jsp, Servlet, 静态图片文件或静态html文件)的请求进行拦截并做出处理,从而实现一些特殊的功能,如登录控制,权限管理,过滤敏感词汇
过滤器可以拆为三部分:
通过Filter技术,开发人员可以实现用户在访问某个目标资源之前,对访问的请求和响应进行拦截。
Servlet API中提供了一个Filter接口
//注意导入时来源选择正确
import javax.servlet.*;
源码如下:
使用Filter,只需实现上面的Filter接口,然后重写里面的方法。
import javax.servlet.*;
import java.io.IOException;
public class MyFilter implements Filter {
import javax.servlet.*;
import java.io.IOException;
public class MyFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
}
@Override
public void destroy() {
}
}
关于这三个方法:
接下来写个示例:
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;
@WebFilter("/*") //过滤路径,WebFilter("/*")表示对所有请求进行过滤
public class MyFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("初始化....."+filterConfig);
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println("过滤request........doSomeFilter");
//下面这行代码的作用就是放行
filterChain.doFilter(servletRequest, servletResponse);
System.out.println("过滤response.......doSomeFilter");
}
@Override
public void destroy() {
System.out.println("销毁了.....destroy");
}
}
Filter的配置可以通过两种方式实现:
FilterRegistrationBean
类来配置接下来先演示第二种方式,FilterRegistrationBean类源码:泛型中的T即我们自己定义的那个MyFilter
先自定义两个Filter:
public class MyFilter1 implements Filter {
...
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
throws IOException, ServletException {
HttpServletRequest request=(HttpServletRequest)servletRequest;
System.out.println("自定义过滤器filter1触发,拦截url:"+request.getRequestURI());
filterChain.doFilter(servletRequest,servletResponse); //放行
}
...
}
第二个Filter:
public class MyFilter2 implements Filter {
...
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
throws IOException, ServletException {
HttpServletRequest request=(HttpServletRequest)servletRequest;
System.out.println("自定义过滤器filter2触发,拦截url:"+request.getRequestURI());
filterChain.doFilter(servletRequest,servletResponse); //放行
}
...
}
创建配置类,返回不同的FilterRegistrationBean实例,来配置上面自定义的过滤器:
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
/**
* @author LLG
* @date 2023/7/23
*/
@Configuration
public class FilterConfig {
@Bean
public FilterRegistrationBean<MyFilter1> filter1FilterRegistration(){
FilterRegistrationBean<MyFilter1> bean = new FilterRegistrationBean<>(new MyFilter1());
/*以上是使用了有参构造,也可以先无参构造,再set我自定义的过滤器进去,如下:
FilterRegistrationBean bean = new FilterRegistrationBean<>();
bean.setFilter(new MyFilter1());*/
bean.setName("MyFilter1"); //过滤器名称
bean.addUrlPatterns("/*"); //过滤所有路径
bean.addInitParameter("exclusions", "/webjars/*,/doc.html"); //设置init方法的参数
//bean.setOrder(1); //优先级,最顶级
bean.setOrder(Ordered.HIGHEST_PRECEDENCE); //也可以直接set一个框架枚举类中的最高优先级(一个最小的负数)
return bean;
}
@Bean
public FilterRegistrationBean<MyFilter2> filter2FilterRegistrationBean(){
FilterRegistrationBean<MyFilter2> bean = new FilterRegistrationBean<>(new MyFilter2());
bean.setName("MyFilter2");
bean.addUrlPatterns("/user/*");
bean.setOrder(Ordered.LOWEST_PRECEDENCE); //最低优先级
return bean;
}
}
测试下效果,先访问localhost:servicePort/
此时只触发了过滤器1,再访问localhost:servicePort/user/3,可以看到两个过滤器都触发了,触发顺序与我设置的优先级一样:
bean.addInitParameter("exclusions", "/webjars/*,/doc.html");
最后再来补充下上面这个addInitParameter方法的用处:即把参数传递给自定义的Filter的init方法的形参filterConfig,filterConfig.getInitParameter(paramName)
拿到参数值后用于后续的逻辑。
@Slf4j
public class MyFilter implements Filter {
private static final String UNKNOWN = "unknown";
private static final String URL_EXCLUSIONS = "exclusions";
private static final String URL_SEPARATOR = ",";
/**
* 排除的 uri
*/
private String[] exclusions;
@Override
public void init(FilterConfig filterConfig) {
//这样就拿到了配置类中要排除的url,即不处理的url
exclusions = filterConfig.getInitParameter(URL_EXCLUSIONS).split(URL_SEPARATOR);
log.info("MyFilter init ...");
}
/**
* 用于判断uri是不是在要排除的uri中
*/
private boolean isExclusion(String uri) {
if (Objects.isNull(uri) || Objects.isNull(exclusions)) {
return false;
}
for (String exclusion : exclusions) {
if (uri.toLowerCase().matches(exclusion.trim().toLowerCase().replace("*", ".*"))) {
return true;
}
}
return false;
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
//转型一下,方便调用自己独有的方法
HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
HttpServletResponse httpServletResponse = (HttpServletResponse) servletResponse;
String requestURI = httpServletRequest.getRequestURI();
//如果是要排除的uri,则直接放行
if (isExclusion(requestURI)) {
filterChain.doFilter(httpServletRequest, httpServletResponse);
} else {
//不是排除的uri,则做自己的处理后再放行
...
}
}
通过init方法的参数,可获得代表当前filter配置信息的FilterConfig对象。
在destroy方法中,可以释放过滤器使用的资源。
关于init方法的形参类型FilterConfig
用户在配置自己的Filter时,可以为Filter配置一些初始化参数,如过滤器名称。当web容器实例化Filter对象,调用其init方法时,会把封装了Filter初始化参数的filterConfig对象传递进来
。我们在写自己的Filter时,就可以通过filterConfig对象,就可以获得配置类中的信息。相关方法如下:
FilterConfig接口中的方法:
1、String getFilterName():得到filter的名称。
2、String getInitParameter(String name): 返回在部署描述中指定名称的初始化参数的值。如果不存在返回null.
3、Enumeration getInitParameterNames():返回过滤器的所有初始化参数的名字的枚举集合。
4、public ServletContext getServletContext():返回Servlet上下文对象的引用。
备份下工作中用到的一个代码,以后方便copy:
@Slf4j
public class RequestLogFilter implements Filter {
private static final String UNKNOWN = "unknown";
private static final String URL_EXCLUSIONS = "exclusions";
private static final String URL_SEPARATOR = ",";
/**
* 排除的 uri
*/
private String[] exclusions;
@Override
public void init(FilterConfig filterConfig) {
exclusions = filterConfig.getInitParameter(URL_EXCLUSIONS).split(URL_SEPARATOR);
log.info("RequestLogFilter init ...");
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
HttpServletResponse httpServletResponse = (HttpServletResponse) servletResponse;
String requestURI = httpServletRequest.getRequestURI();
if (isExclusion(requestURI)) {
filterChain.doFilter(httpServletRequest, httpServletResponse);
} else {
ContentCachingRequestWrapper wrappedRequest = new ContentCachingRequestWrapper((HttpServletRequest) servletRequest);
ContentCachingResponseWrapper wrapperResponse = new ContentCachingResponseWrapper((HttpServletResponse) servletResponse);
LocalDateTime start = LocalDateTime.now();
filterChain.doFilter(wrappedRequest, wrapperResponse);
LocalDateTime end = LocalDateTime.now();
String method = wrappedRequest.getMethod();
String body = getBody(wrappedRequest);
String ip = this.getRequestIp(httpServletRequest);
int status = wrapperResponse.getStatus();
String data = getData(wrapperResponse);
// 返回页面或者文件时,不打印返回结果(但上面getData(wrapperResponse)方法必须执行,否则无法正常返回结果)
if (!"application/json".equals(wrapperResponse.getContentType())){
data = UNKNOWN;
}
log.info("Request Info: url: {}, method: {}, ip: {}, request: {}, response: {}, statusCode: {}, startTime: {}, endTime: {}, cost: {}",
requestURI,
method,
ip,
body,
data,
status,
start.toString(),
end.toString(),
Duration.between(start, end).toMillis()
);
}
}
@Override
public void destroy() {
}
private boolean isExclusion(String uri) {
if (Objects.isNull(uri) || Objects.isNull(exclusions)) {
return false;
}
for (String exclusion : exclusions) {
if (uri.toLowerCase().matches(exclusion.trim().toLowerCase().replace("*", ".*"))) {
return true;
}
}
return false;
}
private String getBody(HttpServletRequest request) {
ContentCachingRequestWrapper requestWrapper = WebUtils.getNativeRequest(request, ContentCachingRequestWrapper.class);
if (requestWrapper != null) {
return getString(requestWrapper.getContentAsByteArray(), request.getCharacterEncoding());
}
return UNKNOWN;
}
private String getData(HttpServletResponse response) throws IOException {
ContentCachingResponseWrapper responseWrapper = WebUtils.getNativeResponse(response, ContentCachingResponseWrapper.class);
if (responseWrapper != null) {
byte[] contentAsByteArray = responseWrapper.getContentAsByteArray();
responseWrapper.copyBodyToResponse();
return getString(contentAsByteArray, "UTF-8");
}
return UNKNOWN;
}
/**
* 如果流里面是个图片或者文件什么的 就是unknown了
*/
private String getString(byte[] contentAsByteArray, String characterEncoding) {
if (contentAsByteArray.length > 0) {
try {
return new String(contentAsByteArray, 0, contentAsByteArray.length, characterEncoding);
} catch (UnsupportedEncodingException ignored) {
}
}
return UNKNOWN;
}
private String getRequestIp(HttpServletRequest request) {
String ip = null;
String ipAddresses = request.getHeader("X-Forwarded-For");
if (ipAddresses == null || ipAddresses.length() == 0 || "unknown".equalsIgnoreCase(ipAddresses)) {
ipAddresses = request.getHeader("Proxy-Client-IP");
}
if (ipAddresses == null || ipAddresses.length() == 0 || "unknown".equalsIgnoreCase(ipAddresses)) {
ipAddresses = request.getHeader("WL-Proxy-Client-IP");
}
if (ipAddresses == null || ipAddresses.length() == 0 || "unknown".equalsIgnoreCase(ipAddresses)) {
ipAddresses = request.getHeader("HTTP_CLIENT_IP");
}
if (ipAddresses == null || ipAddresses.length() == 0 || "unknown".equalsIgnoreCase(ipAddresses)) {
ipAddresses = request.getHeader("X-Real-IP");
}
if (ipAddresses != null && ipAddresses.length() != 0) {
ip = ipAddresses.split(",")[0];
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ipAddresses)) {
ip = request.getRemoteAddr();
}
return ip.equals("0:0:0:0:0:0:0:1") ? "127.0.0.1" : ip;
}
}
配置类:
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
@Configuration
@Slf4j
public class FilterConfiguration {
@Bean
public FilterRegistrationBean<MyFilter> requestLogFilterRegistration() {
FilterRegistrationBean<MyFilter> registration = new FilterRegistrationBean<>(new MyFilter());
registration.addUrlPatterns("/*");
registration.addInitParameter("exclusions", "/webjars/*,/doc.html,/swagger-resources,/swagger-resources/*,/swagger-config/*,/v2/api-docs,/v3/api-docs/*");
registration.setOrder(Ordered.HIGHEST_PRECEDENCE);
return registration;
}
}