一、起源
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的配置类,如下图。
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有众多继承类,下面展示了其中的一部分。
还记得上一篇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
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的默认配置,所以会有自定义配置之外的默认配置。
从上图可以看出,每一个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接口。