导语
OncePerRequestFilter作为SpringMVC中的一个过滤器,在每次请求的时候都会执行。它是对于Filter的抽象实现。比起特定的过滤器来说,这个过滤器对于每次的请求都进行请求过滤。下面就来分析OncePerRequestFilter
OncePerRequestFilter介绍
Filter 拦截器也叫做过滤器,它的作用就是帮助拦截一些用户请求,并对用户请求做一些初步的处理工作全局被初始化一次,这里介绍的OncePerRequestFilter,网上很多的资料都是说它只会拦截一次请求,这里需要更正一点,就是OncePerRequestFilter表示每次的Request都会进行拦截,不管是资源的请求还是数据的请求。有兴趣的可以了解一下Servlet相关的知识。这里主要是对OncePerRequestFilter的说明。
org.springframework.web.filter.OncePerRequestFilter
从上图中可以看到OncePerRequestFilter存在于spring-web模块中,也就是它是Spring框架对于Web Servlet的封装。并且可以看到Spring MVC提供很多的Filter过滤器。其实这些Filter的实现都是大同小异的。下面先来看看Filter接口。
Filter接口 介绍
几乎所有的Filter都是这个接口的实现,对于一个接口来讲就是定义一个规则,接下来它的实现类都是扩展这些规则,完成一些自定义的Filter开发。
public interface Filter {
/**
* Called by the web container to indicate to a filter that it is
* being placed into service.
*
* <p>The servlet container calls the init
* method exactly once after instantiating the filter. The init
* method must complete successfully before the filter is asked to do any
* filtering work.
*
* <p>The web container cannot place the filter into service if the init
* method either
* <ol>
* <li>Throws a ServletException
* <li>Does not return within a time period defined by the web container
* </ol>
*/
public void init(FilterConfig filterConfig) throws ServletException;
/**
* The <code>doFilter</code> method of the Filter is called by the
* container each time a request/response pair is passed through the
* chain due to a client request for a resource at the end of the chain.
* The FilterChain passed in to this method allows the Filter to pass
* on the request and response to the next entity in the chain.
*
* <p>A typical implementation of this method would follow the following
* pattern:
* <ol>
* <li>Examine the request
* <li>Optionally wrap the request object with a custom implementation to
* filter content or headers for input filtering
* <li>Optionally wrap the response object with a custom implementation to
* filter content or headers for output filtering
* <li>
* <ul>
* <li><strong>Either</strong> invoke the next entity in the chain
* using the FilterChain object
* (<code>chain.doFilter()</code>),
* <li><strong>or</strong> not pass on the request/response pair to
* the next entity in the filter chain to
* block the request processing
* </ul>
* <li>Directly set headers on the response after invocation of the
* next entity in the filter chain.
* </ol>
*/
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain)
throws IOException, ServletException;
/**
* Called by the web container to indicate to a filter that it is being
* taken out of service.
*
* <p>This method is only called once all threads within the filter's
* doFilter method have exited or after a timeout period has passed.
* After the web container calls this method, it will not call the
* doFilter method again on this instance of the filter.
*
* <p>This method gives the filter an opportunity to clean up any
* resources that are being held (for example, memory, file handles,
* threads) and make sure that any persistent state is synchronized
* with the filter's current state in memory.
*/
public void destroy();
}
从上面的描述中可以看到,这个接口定义了三个方法,也就是说凡是继承这个接口的类都要实现这三个方法,对于这三个方法而言,最重要的就是doFilter 方法,也就是在实现自定义的Filter的时候最为主要的就是如何去实现这个方法。那么既然OncePerRequestFilter作为它的实现类下面就来看看OncePerRequestFilter是如何实现这个方法
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain)
throws IOException, ServletException;
OncePerRequestFilter类继承关系
结合上面的内容来看一下OncePerRequestFilter是如何实现doFilter()
@Override
public final void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws ServletException, IOException {
//首先判断Request是否是一个HttpServletRequest的请求
if (!(request instanceof HttpServletRequest) || !(response instanceof HttpServletResponse)) {
throw new ServletException("OncePerRequestFilter just supports HTTP requests");
}
//对于请求类型转换
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = (HttpServletResponse) response;
//获取准备过滤的参数名称
String alreadyFilteredAttributeName = getAlreadyFilteredAttributeName();
boolean hasAlreadyFilteredAttribute = request.getAttribute(alreadyFilteredAttributeName) != null;
//如果过滤的参数为空或者跳过Dispatch或者是不做任何的Filter,那么就从筛选器链中找其他的筛选器
if (hasAlreadyFilteredAttribute || skipDispatch(httpRequest) || shouldNotFilter(httpRequest)) {
// Proceed without invoking this filter...
//筛选链
filterChain.doFilter(request, response);
}
//否则执行这个filter
else {
// Do invoke this filter...
//设置标识
request.setAttribute(alreadyFilteredAttributeName, Boolean.TRUE);
//执行内过滤器
try {
//执行内过滤器
doFilterInternal(httpRequest, httpResponse, filterChain);
}
finally {
// Remove the "already filtered" request attribute for this request.
//移除标识
request.removeAttribute(alreadyFilteredAttributeName);
}
}
}
会看到**doFilterInternal()**方法是一个抽象方法,也就是说,继承这个类的子类需要重写这个方法才能完全的实现它的内容。否则功能将不会被实现。
/**
* Same contract as for {@code doFilter}, but guaranteed to be
* just invoked once per request within a single request thread.
* See {@link #shouldNotFilterAsyncDispatch()} for details.
* <p>Provides HttpServletRequest and HttpServletResponse arguments instead of the
* default ServletRequest and ServletResponse ones.
*/
protected abstract void doFilterInternal(
HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException;
如何实现一个自定义的Filter
第一种方式
第一步 实现Filter接口来实现
注意 要是它作为Spring的组件被Spring容器接管,要在类上加上@Component注解,或者使用@Bean注解进行注册
@Component
public class MyFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println(" myfilter init");
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println("myfilter execute");
}
@Override
public void destroy() {
System.out.println("myfilter destroy");
}
}
使用@Bean注入
@Configuration
public class FilterConfig {
@Bean
public FilterRegistrationBean filterRegistration(){
// 新建过滤器注册类
FilterRegistrationBean registration = new FilterRegistrationBean();
// 添加自定义 过滤器
registration.setFilter(globalFilter());
// 设置过滤器的URL模式
registration.addUrlPatterns("/*");
//设置过滤器顺序
registration.setOrder(1);
return registration;
}
@Bean
public GlobalFilter globalFilter(){
return new GlobalFilter();
}
}
第二种方式
第一步 实现Filter接口
@Order(1)
@WebFilter(filterName = "MSecurity",urlPatterns = {"*.html"})
public class MSecurityFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response= (HttpServletResponse) servletResponse;
System.out.println(request.getRequestURI());
//检查是否是登录页面
if(request.getRequestURI().equals("/web/index.html"))
filterChain.doFilter(servletRequest,servletResponse);
//检测用户是否登录
HttpSession session =request.getSession();
String status= (String) session.getAttribute("isLogin");
if(status==null || !status.equals("true"))
{
try{ response.sendRedirect("/web/index.html");}catch (Exception e){}
}
filterChain.doFilter(servletRequest,servletResponse);
}
@Override
public void destroy() {
}
}
上面内容是检查是否登陆如果登陆了就显示index页面如果不是则进入登陆页面
第二步 添加@ServletComponentScan注解
@SpringBootApplication
@ServletComponentScan(basePackages = "com.nihui.security")
public class MsSupplyAndSaleApplication {
public static void main(String[] args) {
SpringApplication.run(MsSupplyAndSaleApplication.class, args);
}
}
内嵌过滤器的使用
这里以一个项目实战的登陆功能最为演示,整合了结合了SpringSecurity的相关知识
第一步 实现WebSecurityConfigurerAdapter的扩展
public class WebSecurityConfigurerAdapterExt extends WebSecurityConfigurerAdapter {
@Autowired
private AuthenticationProvider authenticationProvider;
@Autowired
private AuthenticationSuccessHandler authenticationSuccessHandler;
@Autowired
private AuthenticationFailureHandler authenticationFailureHandler;
@Autowired
private AccessDeniedHandler accessDeniedHandler;
@Autowired
private AuthenticationEntryPoint authenticationEntryPoint;
@Autowired
private LogoutSuccessHandler logoutSuccessHandler;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(authenticationProvider);
}
@Override
protected void configure(HttpSecurity http) throws Exception {
ValidateCodeFilter validateCodeFilter=new ValidateCodeFilter();
http.addFilterBefore(validateCodeFilter, UsernamePasswordAuthenticationFilter.class);
// http.csrf().disable();
http.authorizeRequests()
//Spring Security 5.0 之后需要过滤静态资源
.antMatchers("/mgmt/**").permitAll()
.antMatchers("/swagger*/**","/webjars/**","/api/swagger.json").permitAll()
.antMatchers("/login","/css/**","/js/**","/img.*").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.usernameParameter("loginName").passwordParameter("password")
.successHandler(authenticationSuccessHandler)
.failureHandler(authenticationFailureHandler)
.and()
.logout().logoutSuccessHandler(logoutSuccessHandler).permitAll()
.and()
.exceptionHandling()
.accessDeniedHandler(accessDeniedHandler)
.authenticationEntryPoint(authenticationEntryPoint);
}
}
第二步 向SpringBoot中注入扩展配置
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class MyWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapterExt {
@Override
public void configure(WebSecurity web) throws Exception {
// TODO Auto-generated method stub
super.configure(web);
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
http.headers().frameOptions().sameOrigin();
// super.configure(http);
//http 请求认证操作
http.authorizeRequests()
//Spring Security 5.0 之后需要过滤静态资源
.antMatchers("/login").permitAll()
.requestMatchers(CorsUtils::isPreFlightRequest).permitAll()
.anyRequest().authenticated()
// .anyRequest().permitAll()
.and()
.addFilterAfter(new DeptSelectFilter(), SessionManagementFilter.class)
.formLogin()
.usernameParameter("loginName").passwordParameter("password")
.successHandler(authenticationSuccessHandler)
.failureHandler(authenticationFailureHandler)
.authenticationDetailsSource(authenticationDetailsSource)
.and()
.logout().logoutSuccessHandler(logoutSuccessHandler).permitAll()
.and()
.exceptionHandling()
.accessDeniedHandler(accessDeniedHandler)
.authenticationEntryPoint(authenticationEntryPoint);
}
//用户服务扩展
@Bean
public UserDetailsServiceExt userDetailsServiceExt() {
return new UserConsumerAuthServiceImpl();
}
}
第三步 设置一个登陆后置拦截器 DeptSelectFilter
这个拦截器被设置到了登陆验证完成之后,用户用来选择对应的部门,如果部门认证通过的话就进入到index页面如果没有经过任何的处理,也就是说第一次登陆就会引导用户选择则对应的部门,并且回传部门信息。
public class DeptSelectFilter extends OncePerRequestFilter {
// @Autowired
// private UserInfoFeignClient userInfoFeignClient;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
boolean validDept = false;
//获取请求的URI字符串
String requesturi = request.getRequestURI();
//userInfoFeignClient.getUserInfo()
//判断是否登录
if(SecurityContextHolderExt.isLogin()) {
if("/dept".equals(requesturi)) {
validDept = false;
//匹配字符串内容为 /v[数字]/dept.*
}else if(requesturi.matches("/v[0-9]+/dept.*")) {
validDept = false;
}else {
validDept = true;
}
}
if(validDept){
List<DeptExt> deptExts = SecurityContextHolderExt.getDepts();
if(deptExts==null || deptExts.size()==0) {
if(AjaxUtil.isAjaxRequest(request)) {
ResultResp<UserResp> resultResp=new ResultResp<>();
ExceptionMsg exceptionMsg=new ExceptionMsg();
exceptionMsg.setErrorMsg("请先选择部门");
resultResp.setExceptionMsg(exceptionMsg);
resultResp.setStatus(ResultRespStatus.EXCEPTION);
ResponseUtil.doResponse(response, HttpServletResponse.SC_UNAUTHORIZED, MediaType.APPLICATION_JSON_VALUE, resultResp.toString());
return;
}else {
response.sendRedirect("/dept");
return;
}
}
}
filterChain.doFilter(request, response);
}
}
上面方法就实现了对OncePerRequestFilter拦截器的doFilterInternal()方法的扩展,并且最后结束的时候将请求引入到了Filter链路中。filterChain.doFilter(request, response)。
OncePerRequestFilter 类继承关系扩展
通过上图的类关系图可以看到,在SpringMVC中对于Filter的扩展都是继承了OncePerRequestFilter。其中都是实现了doFilterInternal()的方法扩展。
总结
上面内容主要讲述了在实际的开发中如何使用OncePerRequestFilter过滤器。并且结合了一个小例子,描述了在实际开发中如何使用Filter,当然在实际开发中使用到Filter的场景还有其他的使用场景。这里只是这个应用场景的冰山一角。