The problem of multiple executions of Spring Security custom filters

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);
        }
    }
}

When we inherit Filter directly or indirectly, Spring Security will automatically add the filter to the multi-execution list.
If we still add this to the 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();
    }
}

will execute this filter word twice.

dividing line

insert image description here
A friend pointed out the problem. I will read the above statement and feel that there are relatively big problems in the previous records. I didn't put up how the conclusion was obtained, and now I can't remember it in retrospect. Simply study the operating mechanism of the filter in Spring again.
First, starting from the problem that the custom Filter was executed twice, we found that the two executions were two different filter chains: FilterChainProxy and ApplicationFilterChain. As shown in the following two pictures.
Please add a picture description
Please add a picture description
Next, we need to understand these two chains.
The first is FilterChainProxy:
the following code is the doFilter method of FilterChainProxy, which is the chain.doFilter called by the doFilter method of our TokenFilter.

	@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);
			}
		}
	}

From the above code, we can see that when currentPosition == size, that is, after all the filters inside the chain are executed, another filterChain->
originalChain will appear next. And this originalChain is just ApplicationFilterChain. Naturally, the Filter in the ApplicationFilterChain will be executed next, and both FilterChains contain the TokenFilter we defined.
insert image description here
I understand that it is because there are customFilter problems in both FilterChains, but why is this the case.
We added FilterChainProxy through addFilterBefore
and found through debug that
ApplicationFilterChain was created in the invoke method of StandardWrapperValue

ApplicationFilterChain filterChain =
                ApplicationFilterFactory.createFilterChain(request, wrapper, servlet);

The value of wrapper here is the key
insert image description here
StandardEngine[Tomcat].StandardHost[localhost].TomcatEmbeddedContext[].StandardWrapper[dispatcherServlet]
Continue to look down to see the inside of ApplicationFilterFactory.createFilterChain()
There are two lines of code in line 83 of ApplicationFilterFactory:

StandardContext context = (StandardContext) wrapper.getParent();
        FilterMap filterMaps[] = context.findFilterMaps();

insert image description here
Through these two lines of code, we can know that the Filter source of ApplicationChain is the value of wrapper.parent.filterMaps.
Lines 109-115 of ApplicationFilterFactory traverse the map and add the value to ApplicationFilterChain.

Now the question comes to when wrapper.parent.filterMaps is initialized.
StandardWrapperValue103 line

StandardWrapper wrapper = (StandardWrapper) getContainer();

insert image description here
Add FilterMap to TomcatEmbeddedContext through ApplicationContext.addFilter. Here is not through addFilterBefore. In what way, if you are interested, you can explore it in depth.
Roughly need to meet
1. Filter must be a Spring Bean object before it will be added.
2. Must be a Filter.

Guess you like

Origin blog.csdn.net/weixin_44225096/article/details/124594824