public class TokenFilter implements Filter {
public static final String HEADER_AUTH_NAME = "Authorization";
@Autowired
JWTProvider jwtProvider;
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) {
try {
HttpServletRequest httpServletRequest = (HttpServletRequest) req;
String authToken = httpServletRequest.getHeader(HEADER_AUTH_NAME);
if (StringUtils.isNotBlank(authToken)) {
// 从自定义tokenProvider中解析用户
Authentication authentication = this.jwtProvider.getAuthentication(authToken);
SecurityContextHolder.getContext().setAuthentication(authentication);
}
// 调用后续的Filter,如果上面的代码逻辑未能复原“session”,SecurityContext中没有想过信息,后面的流程会检测出"需要登录"
chain.doFilter(req, res);
} catch (Exception ex) {
throw new RuntimeException(ex);
}
}
}
当我们直接或间接的继承了Filter之后,Spring Security 会自动将过滤器添加多执行列表中。
如果我们仍将其添加到 WebSecurityConfigurerAdapter 中
@Configuration
public class MySecurityConfiguration extends WebSecurityConfigurerAdapter {
@Override
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Autowired
private LoginAuthenticationFilter loginAuthenticationFilter;
@Autowired
private TokenFilter tokenFilter;
@Autowired
private CorsConfigurationSource configurationSource;
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.addFilterBefore(loginAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)
// .addFilterBefore(tokenFilter,UsernamePasswordAuthenticationFilter.class)
.authorizeRequests()
.and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
.and()
.authorizeRequests()
.antMatchers("/login","/register").permitAll()
.anyRequest().authenticated()
.and()
.cors().configurationSource(configurationSource)
// 关闭 csrf 保护
.and()
.csrf().disable()
.logout()
.logoutUrl("/logout")
.logoutSuccessHandler(new LogoutSuccessHandler() {
@Override
public void onLogoutSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
// 返回 json 格式的数据
httpServletResponse.setContentType("application/json;charset=utf-8");
PrintWriter writer = httpServletResponse.getWriter();
Map<String, Object> map = new HashMap<>(16);
map.put("status:", 200);
map.put("msg:","注销登录成功!");
writer.write(new ObjectMapper().writeValueAsString(map));
writer.flush();
writer.close();
}
});
}
@Bean
public PasswordEncoder passwordEncoderBean() {
return new BCryptPasswordEncoder();
}
@Bean
@Override
protected AuthenticationManager authenticationManager() throws Exception {
return super.authenticationManager();
}
}
就会对此过滤词执行两次。
分界线
有个朋友指出了问题,我会看了上面的说法,感觉之前的记录有比较大的问题。并没有把结论是怎么得来的放上去,现在回想也想不起来了。索性再次研究一下Spring中filter的运行机制。
首先从自定义的Filter执行了两次的问题出发,我们发现,这两次的执行是两条不同的过滤器链分别为:FilterChainProxy和ApplicationFilterChain。如下面两张图。
接下来我们就要了解一下这两条链
首先是FilterChainProxy:
下面这段代码是FilterChainProxy的doFilter方法也就是我们的TokenFilter的doFilter方法调用的chain.doFilter。
@Override
public void doFilter(ServletRequest request, ServletResponse response)
throws IOException, ServletException {
if (currentPosition == size) {
if (logger.isDebugEnabled()) {
logger.debug(UrlUtils.buildRequestUrl(firewalledRequest)
+ " reached end of additional filter chain; proceeding with original chain");
}
// Deactivate path stripping as we exit the security filter chain
this.firewalledRequest.reset();
originalChain.doFilter(request, response);
}
else {
currentPosition++;
Filter nextFilter = additionalFilters.get(currentPosition - 1);
if (logger.isDebugEnabled()) {
logger.debug(UrlUtils.buildRequestUrl(firewalledRequest)
+ " at position " + currentPosition + " of " + size
+ " in additional filter chain; firing Filter: '"
+ nextFilter.getClass().getSimpleName() + "'");
}
nextFilter.doFilter(request, response, this);
}
}
}
从上面的代码我们可以看出当currentPosition == size时即chain内部的filter都执行之后,接下来会出现另外一个filterChain->
originalChain。而这个originalChain正是ApplicationFilterChain。自然的,接下来就会执行到ApplicationFilterChain中Filter,而这两个FilterChain中都包含了我们定义的TokenFilter。
了解到是因为两条FilterChain中都有customFilter的问题,但是为什么会是这样呢。
FilterChainProxy时我们通过addFilterBefore添加的
同时通过debug发现,
ApplicationFilterChain是StandardWrapperValue的invoke方法内创建的
ApplicationFilterChain filterChain =
ApplicationFilterFactory.createFilterChain(request, wrapper, servlet);
这里wrapper的值是关键
StandardEngine[Tomcat].StandardHost[localhost].TomcatEmbeddedContext[].StandardWrapper[dispatcherServlet]
继续往下看来到ApplicationFilterFactory.createFilterChain()内部
在ApplicationFilterFactory的第83行有这样两行代码:
StandardContext context = (StandardContext) wrapper.getParent();
FilterMap filterMaps[] = context.findFilterMaps();
通过这两行代码可知ApplicationChain的Filter来源是wrapper.parent.filterMaps的值。
在ApplicationFilterFactory的109-115行是遍历这个map,将值add到ApplicationFilterChain中。
现在问题来到了wrapper.parent.filterMaps是何时初始化的问题上。
StandardWrapperValue103行
StandardWrapper wrapper = (StandardWrapper) getContainer();
通过ApplicationContext.addFilter向TomcatEmbeddedContext中添加FilterMap。这里不是通过addFilterBefore。具体通过什么方式,感兴趣的可以再去深入探究一下。
大致需要满足
1.Filter必须是Spring的Bean对象,才会被添加。
2.必须是Filter。