Spring Security自定义过滤器多次执行的问题

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。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值