一、起源 

1.1 配置

一个简单的WebSecurity配置,重载了三个config方法。分别配置了登录方式、用户来源和过滤特定url。

@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private PasswordEncoder passwordEncoder;
    
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
                .anyRequest().authenticated()
            .and()
                .httpBasic();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth
            .inMemoryAuthentication()
                .withUser("admin")
                .password(passwordEncoder.encode("123456"))
                .authorities("admin");
    }

    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers("/css/**", "/js/**", "/favicon.ico");
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return PasswordEncoderFactories.createDelegatingPasswordEncoder();
    }
}

1.2 源头

1.2.1 SpringBootAutoconfigure

在使用SpringBoot时,会引用或者间接引用到包SpringBootAutoconfigure,这个包包含了一些常用的jar包的默认配置,如大家熟悉的server.port=8080。SpringSecurity的默认配置也会在SpringBootAutoconfigure包内。
SpringBootAutoconfigure包会加载一系列配置类,也包括SpringSecurity的配置类,如下图。

Spring Security5 有效时间 springsecurity过期时间_ico

1.2.2 SecurityAutoConfiguration

其中有一个SecurityAutoConfiguration类,会在项目初始化是被加载。从下面的源代码可以看到SecurityAutoConfiguration类import了WebSecurityEnablerConfiguration类。

@Configuration
@ConditionalOnClass(DefaultAuthenticationEventPublisher.class)
@EnableConfigurationProperties(SecurityProperties.class)
@Import({ SpringBootWebSecurityConfiguration.class, WebSecurityEnablerConfiguration.class,
		SecurityDataConfiguration.class })
public class SecurityAutoConfiguration {
	...
}

1.2.3 WebSecurityEnablerConfiguration

WebSecurityEnablerConfiguration类加上了一个EnableWebSecurity注解

@Configuration
@EnableWebSecurity
public class WebSecurityEnablerConfiguration {

}

1.2.4 EnableWebSecurity

EnableWebSecurity注解又import了WebSecurityConfiguration类

...
@Import({ WebSecurityConfiguration.class,
		SpringWebMvcImportSelector.class,
		OAuth2ImportSelector.class })
@EnableGlobalAuthentication
@Configuration
public @interface EnableWebSecurity {
	...
}

1.2.5 WebSecurityConfiguration

WebSecurityConfiguration类中,有两个比较重要的步骤,如下。

@Configuration
public class WebSecurityConfiguration implements ImportAware, BeanClassLoaderAware {
	...
	@Bean(name = AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME)
	public Filter springSecurityFilterChain() throws Exception {
		// 这里的webSecurityConfigurers就是我们在程序里的SpringSecurity配置
		boolean hasConfigurers = webSecurityConfigurers != null
				&& !webSecurityConfigurers.isEmpty();
		if (!hasConfigurers) {
			WebSecurityConfigurerAdapter adapter = objectObjectPostProcessor
					.postProcess(new WebSecurityConfigurerAdapter() {
					});
			webSecurity.apply(adapter);
		}
		return webSecurity.build();
	}
	...
	@Autowired(required = false)
	public void setFilterChainProxySecurityConfigurer(
			ObjectPostProcessor<Object> objectPostProcessor,
			@Value("#{@autowiredWebSecurityConfigurersIgnoreParents.getWebSecurityConfigurers()}") List<SecurityConfigurer<Filter, WebSecurity>> webSecurityConfigurers)
			throws Exception {
		webSecurity = objectPostProcessor
			.postProcess(new WebSecurity(objectPostProcessor));
		...
		for (SecurityConfigurer<Filter, WebSecurity> webSecurityConfigurer : webSecurityConfigurers) {
			webSecurity.apply(webSecurityConfigurer);
		}
		this.webSecurityConfigurers = webSecurityConfigurers;
	}
	...
}
  • 一个是setFilterChainProxySecurityConfigurer方法,负责初始化webSecurity和webSecurityConfigurers。
  • webSecurity可以理解为顶层的WebSecurity配置,webSecurityConfigurers是通过beanFactory找到的所有继承WebSecurityConfigurer接口的类,也包括我们自定义的WebSecurity配置。
  • 实例化后的顶层webSecurity,再应用其它的webSecurityConfigurers配置。

接下来谈谈另一个重要的方法springSecurityFilterChain的Bean注入。这个springSecurityFilterChain会调用webSecurity的build方法,会根据自定义的WebSecurity配置,来建造相应的规则。

二、诞生

springSecurityFilterChain的形成,最终包含了4个filterChain。其中HttpSecurity生成的filterChainer,包含了多个filter。

主要介绍WebSecurity的规则是如何从自定义配置规则加入到整个SpringSecurity的认证链条的。

2.1 配置

WebSecurity配置

@Configuration
public class MyWebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private PasswordEncoder passwordEncoder;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // @formatter:off
        http
            .authorizeRequests()
                .anyRequest().authenticated()
            .and()
                .httpBasic();
        // @formatter:on
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {

        // @formatter:off
        auth
            .inMemoryAuthentication()
                .withUser("admin")
                .password(passwordEncoder.encode("123456"))
                .authorities("admin");
        // @formatter:on
    }

    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers("/css/**", "/js/**", "/favicon.ico");
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return PasswordEncoderFactories.createDelegatingPasswordEncoder();
    }
}

2.2 配置方法

上面的三个config配置方法里,应用了许多配置项。
比如httpBasic()方法,从下面的源代码可以看出,它会实例化HttpBasicConfigurer类,并应用到HttpSecurity里。

public HttpBasicConfigurer<HttpSecurity> httpBasic() throws Exception {
	return getOrApply(new HttpBasicConfigurer<>());
}

又比如inMemoryAuthentication()方法。

public InMemoryUserDetailsManagerConfigurer<AuthenticationManagerBuilder> inMemoryAuthentication()
			throws Exception {
	return apply(new InMemoryUserDetailsManagerConfigurer<>());
}

无论是HttpBasicConfigurer还是InMemoryUserDetailsManagerConfigurer,都有同一个父类SecurityBuilder。

2.3 builder设计模式

SecurityBuilder类是一个建造者类,只有一个build方法。

public interface SecurityBuilder<O> {
	O build() throws Exception;
}

SecurityBuilder有众多继承类,下面展示了其中的一部分。

Spring Security5 有效时间 springsecurity过期时间_ico_02

还记得上一篇setFilterChainProxySecurityConfigurer方法创建的webSeurity对象,它也继承SecurityBuilder类。当webSeurity.build()后,就会引发它下面所有的SecurityBuild继承类的调用build方法,如上面说到的HttpBasicConfigurer和InMemoryUserDetailsManagerConfigurer类。

2.4 webSecurity构建诞生

webSecurity.build()是怎么一步步应用自定义MyWebSecurityConfig 配置三个方法的。

@Bean(name = AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME)
public Filter springSecurityFilterChain() throws Exception {
	boolean hasConfigurers = webSecurityConfigurers != null
			&& !webSecurityConfigurers.isEmpty();
	if (!hasConfigurers) {
		WebSecurityConfigurerAdapter adapter = objectObjectPostProcessor
				.postProcess(new WebSecurityConfigurerAdapter() {
				});
		webSecurity.apply(adapter);
	}
	return webSecurity.build();
}

2.4.1 三个自定义配置

1) configure(AuthenticationManagerBuilder auth)

WebSecurity.build() ->
AbstractSecurityBuilder.build() ->
AbstractConfiguredSecurityBuilder.doBuild() ->
AbstractConfiguredSecurityBuilder.init()->
WebSecurityConfigurerAdapter.init(final WebSecurity web)->
WebSecurityConfigurerAdapter.getHttp()->
WebSecurityConfigurerAdapter.authenticationManager()->
自定义WebSecurityConfig->configure(AuthenticationManagerBuilder auth)

2) configure(HttpSecurity http)

然后是来到的configure(HttpSecurity http)方法。

WebSecurity.build() ->
AbstractSecurityBuilder.build() ->
AbstractConfiguredSecurityBuilder.doBuild() ->
AbstractConfiguredSecurityBuilder.init()->
WebSecurityConfigurerAdapter.init(final WebSecurity web)->
WebSecurityConfigurerAdapter.getHttp()->
自定义WebSecurityConfig->configure(HttpSecurity http)

3) configure(WebSecurity web)

最后是来到的configure(WebSecurity http)方法。

WebSecurity.build() ->
AbstractSecurityBuilder.build() ->
AbstractConfiguredSecurityBuilder.doBuild() ->
AbstractConfiguredSecurityBuilder.configure()->
自定义WebSecurityConfig->configure(WebSecurity web)

FilterChainProxy

在springSecurityFilterChain Bean的构建中,调用下面的performBuild()方法, 创建FilterChainProxy实例,并添加自定义配置到securityFilterChains中。ignoredRequests就是z自定义配置configure(WebSecurity web)方法中的web.ignoring().antMatchers("/css/", "/js/", “/favicon.ico”);而securityFilterChainBuilders就是自定义配置的configure(HttpSecurity http)。

protected Filter performBuild() throws Exception {
	int chainSize = ignoredRequests.size() + securityFilterChainBuilders.size();
	List<SecurityFilterChain> securityFilterChains = new ArrayList<>(
			chainSize);
	for (RequestMatcher ignoredRequest : ignoredRequests) {
		securityFilterChains.add(new DefaultSecurityFilterChain(ignoredRequest));
	}
	for (SecurityBuilder<? extends SecurityFilterChain> securityFilterChainBuilder : securityFilterChainBuilders) {
		securityFilterChains.add(securityFilterChainBuilder.build());
	}
	FilterChainProxy filterChainProxy = new FilterChainProxy(securityFilterChains);

	Filter result = filterChainProxy;
	postBuildAction.run();
	return result;
}

2.4.2 ignoredRequest

下图可以清楚的看到3个ignoredRequest

Spring Security5 有效时间 springsecurity过期时间_自定义_03

2.4.3 securityFilterChain

securityFilterChain情况就比较复杂了,会合并我们自定义配置和默认配置

1) 默认配置

在WebSecurity.build()方法被调用时,还有一段SpringSecurity设置默认配置的代码,如下

protected final HttpSecurity getHttp() throws Exception {
	...
	http = new HttpSecurity(objectPostProcessor, authenticationBuilder,
			sharedObjects);
	if (!disableDefaults) {
		// @formatter:off
		http
			.csrf().and()                                       // 1
			.addFilter(new WebAsyncManagerIntegrationFilter())  // 2
			.exceptionHandling().and()                          // 3
			.headers().and()                                    // 4
			.sessionManagement().and()                          // 5
			.securityContext().and()                            // 6
			.requestCache().and()                               // 7
			.anonymous().and()                                  // 8
			.servletApi().and()                                 // 9
			.apply(new DefaultLoginPageConfigurer<>()).and()    //10
			.logout();                                          //11
		// @formatter:on
		...
	}
	configure(http);
	return http;
}

从上面SpringSecurity提供的默认配置可以看出,SpringSecurity默认地为我们添加了11个SecurityConfigurer和Filter。

2) SecurityConfigurer和Filter

以SpringSecurity提供的默认配置,csrf()方法为例。
首先,csrf()方法应用了一个CsrfConfigurer配置类,这个类继承自SecurityConfigurer

public final class HttpSecurity extends
	AbstractConfiguredSecurityBuilder<DefaultSecurityFilterChain, HttpSecurity>
	implements SecurityBuilder<DefaultSecurityFilterChain>,
	HttpSecurityBuilder<HttpSecurity> {
	...
	public CsrfConfigurer<HttpSecurity> csrf() throws Exception {
		ApplicationContext context = getContext();
		return getOrApply(new CsrfConfigurer<>(context));
	}
	...
}

接着,CsrfConfigurer实现了configure(Hhttp)方法,此方法会实例化一个CsrfFilter。

public final class CsrfConfigurer<H extends HttpSecurityBuilder<H>>
		extends AbstractHttpConfigurer<CsrfConfigurer<H>, H> {
	...
	@Override
	public void configure(H http) throws Exception {
		CsrfFilter filter = new CsrfFilter(this.csrfTokenRepository);
		RequestMatcher requireCsrfProtectionMatcher = getRequireCsrfProtectionMatcher();
		if (requireCsrfProtectionMatcher != null) {
			filter.setRequireCsrfProtectionMatcher(requireCsrfProtectionMatcher);
		}
		AccessDeniedHandler accessDeniedHandler = createAccessDeniedHandler(http);
		if (accessDeniedHandler != null) {
			filter.setAccessDeniedHandler(accessDeniedHandler);
		}
		LogoutConfigurer<H> logoutConfigurer = http.getConfigurer(LogoutConfigurer.class);
		if (logoutConfigurer != null) {
			logoutConfigurer
					.addLogoutHandler(new CsrfLogoutHandler(this.csrfTokenRepository));
		}
		SessionManagementConfigurer<H> sessionConfigurer = http
				.getConfigurer(SessionManagementConfigurer.class);
		if (sessionConfigurer != null) {
			sessionConfigurer.addSessionAuthenticationStrategy(
					new CsrfAuthenticationStrategy(this.csrfTokenRepository));
		}
		filter = postProcess(filter);
		http.addFilter(filter);
	}
	...
}

最终,这个Filter会被添加到到http,而这个http就是HttpSecurity类,是本篇在开头提到的getHttp()方法中实例出来的,同时也是我们的自定义配置类中可以看到的那个HttpSecurity http。

2.4.4 最终产物

springSecurityFilterChain是一个Bean。如果按照自定义配置,它会包括4个DefaultSecurityFilterChain。

其中三个filterChain是自定义配置的web.ignoring().antMatchers("/css/", "/js/", “/favicon.ico”),另外一个是自定义配置的configure(HttpSecurity http)。

HttpSecurity因为保留了SpringSecurity的默认配置,所以会有自定义配置之外的默认配置。

Spring Security5 有效时间 springsecurity过期时间_前端_04

从上图可以看出,每一个HttpRequest都会经过4个FilterChain。

三、出征

主要介绍Spring Security是怎么对request请求进行拦截处理的。

3.1 入口

当客户端发送http request请求时,就会进入之前通过SpringSecurity创建的FilterChainProxy的doFilter方法,再进入doFilterInternal方法。

public class FilterChainProxy extends GenericFilterBean {
	@Override
	public void doFilter(ServletRequest request, ServletResponse response,
			FilterChain chain) throws IOException, ServletException {
		boolean clearContext = request.getAttribute(FILTER_APPLIED) == null;
		if (clearContext) {
			try {
				request.setAttribute(FILTER_APPLIED, Boolean.TRUE);
				doFilterInternal(request, response, chain);
			}
			finally {
				SecurityContextHolder.clearContext();
				request.removeAttribute(FILTER_APPLIED);
			}
		}
		else {
			doFilterInternal(request, response, chain);
		}
	}
	
	private void doFilterInternal(ServletRequest request, ServletResponse response,
			FilterChain chain) throws IOException, ServletException {

		FirewalledRequest fwRequest = firewall
				.getFirewalledRequest((HttpServletRequest) request);
		HttpServletResponse fwResponse = firewall
				.getFirewalledResponse((HttpServletResponse) response);

		List<Filter> filters = getFilters(fwRequest);

		if (filters == null || filters.size() == 0) {
			if (logger.isDebugEnabled()) {
				logger.debug(UrlUtils.buildRequestUrl(fwRequest)
						+ (filters == null ? " has no matching filters"
								: " has an empty filter list"));
			}

			fwRequest.reset();

			chain.doFilter(fwRequest, fwResponse);

			return;
		}

		VirtualFilterChain vfc = new VirtualFilterChain(fwRequest, chain, filters);
		vfc.doFilter(fwRequest, fwResponse);
	}
}

接着,进入VirtualFilterChain类的doFilter方法

private static class VirtualFilterChain implements FilterChain {
	@Override
	publicvoid 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);
		}
	}
}

上面的additionalFilters是生成的12个filters。上面的nextFilter就是在循环这12个filters,并在每一次循环中调用它们的doFilter方法。

3.2 GenericFilterBean

GenericFilterBean类是继承javax.servlet.Filter接口的。也就会在Tomcat接收到HttpRequest请求时,就调用Filter接口的doFilter方法。

public interface Filter {

    public default void init(FilterConfig filterConfig) throws ServletException {}

    public void doFilter(ServletRequest request, ServletResponse response,
            FilterChain chain) throws IOException, ServletException;

    public default void destroy() {}
}

3.3 自定义Filter

有兴趣的读者可以试一下下面的代码,当tomcat收到httpRequest的时候,就会触发doFilter方法。

@Component
public class MyFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        System.out.println("Do Filter");
        chain.doFilter(request, response);
    }

    @Override
    public void destroy() {

    }
}

3.4 总结

SpringSecurity之所以能过滤,主要还是继承了javax.servlet.Filter接口。