前言
接着上一篇,我們來談談WebSecurity的規則是如何從我們配置規則加入到整個SpringSecurity的認證鏈條的。
配置
回顧一下上一篇那個簡單的WebSecurity配置
@Configuration
public class WebSecurityConfig 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();
}
}
配置方法
上面的三個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。
builder設計模式
SecurityBuilder類是一個建造者類,只有一個build方法。
public interface SecurityBuilder<O> {
O build() throws Exception;
}
SecurityBuilder有衆多繼承類,下面展示了其中的一部分。
還記得上一篇setFilterChainProxySecurityConfigurer方法創建的webSeurity對象,它也繼承SecurityBuilder類。當webSeurity.build()後,就會引發它下面所有的SecurityBuild繼承類的調用build方法,如上面說到的HttpBasicConfigurer和InMemoryUserDetailsManagerConfigurer類。
誕生
開始進入主題,看看webSecurity.build()是怎麼一步步應用我們的配置三個方法的。
@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();
}
1. 三個自定義配置
1.1 configure(AuthenticationManagerBuilder auth)
先看看是如何一步步來到的configure(AuthenticationManagerBuilder auth)方法。
WebSecurity.build() ->
AbstractSecurityBuilder.build() ->
AbstractConfiguredSecurityBuilder.doBuild() ->
AbstractConfiguredSecurityBuilder.init()->
WebSecurityConfigurerAdapter.init(final WebSecurity web)->
WebSecurityConfigurerAdapter.getHttp()->
WebSecurityConfigurerAdapter.authenticationManager()->
自定義WebSecurityConfig->configure(AuthenticationManagerBuilder auth)
1.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)
1.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就是我們配置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. ignoredRequest
下圖可以清楚的看到3個ignoredRequest
3. securityFilterChain
securityFilterChain情況就比較複雜了,會合並我們自定義配置和默認配置
3.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。
3.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(H http)方法,此方法會實例化一個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。
4. 最終產物
上篇提到的springSecurityFilterChain是一個Bean。如果按照我們自定義的配置,它會包括4個DefaultSecurityFilterChain。其中三個filterChain是我們配置的web.ignoring().antMatchers("/css/", "/js/", “/favicon.ico”),另外一個是我們配置的configure(HttpSecurity http)。HttpSecurity因爲保留了SpringSecurity的默認配置,所以會有我們配置之外的默認配置。
從上圖可以看出,每一個HttpRequest都會經過4個FilterChain。
總結
本篇介紹了springSecurityFilterChain的形成,它最終包含了4個filterChain。其中HttpSecurity生成的filterChainer,包含了多個filter。那麼下篇再談談這些Filter是怎麼幫助我們來攔截或者處理HttpRequest的。