文章目录
  1. 1. 基于源码的流程分析
    1. 1.1. 入口 WebSecurityConfiguration
    2. 1.2. 配置HttpSecurity
    3. 1.3. 构建Filter
  2. 2. 相关的一些问题
    1. 2.1. Spring Boot的H2ConsoleAutoConfiguration导致页面标签的权限控制不正常

基于源码的流程分析

下面的源码基于Spring Security 4.0.3版本。

入口 WebSecurityConfiguration

WebSecurityConfiguration的目的是配置WebSecurity来创建[FilterChainProxy][FilterChainProxy]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
@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));
if (debugEnabled != null) {
webSecurity.debug(debugEnabled);
}
Collections.sort(webSecurityConfigurers, AnnotationAwareOrderComparator.INSTANCE);
Integer previousOrder = null;
for (SecurityConfigurer<Filter, WebSecurity> config : webSecurityConfigurers) {
Integer order = AnnotationAwareOrderComparator.lookupOrder(config);
if (previousOrder != null && previousOrder.equals(order)) {
throw new IllegalStateException(
"@Order on WebSecurityConfigurers must be unique. Order of "
+ order + " was already used, so it cannot be used on "
+ config + " too.");
}
previousOrder = order;
}
for (SecurityConfigurer<Filter, WebSecurity> webSecurityConfigurer : webSecurityConfigurers) {
webSecurity.apply(webSecurityConfigurer);
}
this.webSecurityConfigurers = webSecurityConfigurers;
}

这里会对WebSecurity套用所有的SecurityConfigurer的实例,包括自定义的继承了WebSecurityConfigurerAdapter的自定义配置。

这里的套用的过程只是把实例添加到configurers属性中

1
2
3
4
public <C extends SecurityConfigurer<O, B>> C apply(C configurer) throws Exception {
add(configurer);
return configurer;
}

最终在build的过程中会初始化这些配置并套用。

配置HttpSecurity

WebSecurityConfigurerAdapter会初始化一个默认的HttpSecurity,同时会调用protected void configure(HttpSecurity http)来进行自定义的设置。最终HttpSecurity的实例会作为SecurityFilterChainBuilder传入WebSecurity,以及调用postBuildAction来设置FilterSecurityInterceptor。

1
2
3
4
5
6
7
8
9
10
public void init(final WebSecurity web) throws Exception {
final HttpSecurity http = getHttp();
web.addSecurityFilterChainBuilder(http).postBuildAction(new Runnable() {
public void run() {
FilterSecurityInterceptor securityInterceptor = http
.getSharedObject(FilterSecurityInterceptor.class);
web.securityInterceptor(securityInterceptor);
}
});
}

WebSecurity中的FilterSecurityInterceptor最终还会用来构建WebInvocationPrivilegeEvaluator用于页面标签的权限控制。从目前源码上来看,页面标签的WebInvocationPrivilegeEvaluator只会使用最后设置到WebSecurity中的FilterSecurityInterceptor。如果有多个WebSecurityConfigurerAdapter的子类实例,那么只有最后一个HttpSecurity中的FilterSecurityInterceptor才会生效,其余的会被覆盖。所以,页面标签的权限控制要注意HttpSecurity配置的顺序,WebSecurityConfigurerAdapter默认优先度是100

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
/**
* Creates the {@link HttpSecurity} or returns the current instance
*
* ] * @return the {@link HttpSecurity}
* @throws Exception
*/
protected final HttpSecurity getHttp() throws Exception {
if (http != null) {
return http;
}
DefaultAuthenticationEventPublisher eventPublisher = objectPostProcessor
.postProcess(new DefaultAuthenticationEventPublisher());
localConfigureAuthenticationBldr.authenticationEventPublisher(eventPublisher);
AuthenticationManager authenticationManager = authenticationManager();
authenticationBuilder.parentAuthenticationManager(authenticationManager);
http = new HttpSecurity(objectPostProcessor, authenticationBuilder,
localConfigureAuthenticationBldr.getSharedObjects());
http.setSharedObject(UserDetailsService.class, userDetailsService());
http.setSharedObject(ApplicationContext.class, context);
http.setSharedObject(ContentNegotiationStrategy.class, contentNegotiationStrategy);
http.setSharedObject(AuthenticationTrustResolver.class, trustResolver);
if (!disableDefaults) {
// @formatter:off
http
.csrf().and()
.addFilter(new WebAsyncManagerIntegrationFilter())
.exceptionHandling().and()
.headers().and()
.sessionManagement().and()
.securityContext().and()
.requestCache().and()
.anonymous().and()
.servletApi().and()
.apply(new DefaultLoginPageConfigurer<HttpSecurity>()).and()
.logout();
// @formatter:on
}
configure(http);
return http;
}

构建Filter

WebSecurity最终会构建Servlet Filter。HttpSecurity的performBuild则最终会构建DefaultSecurityFilterChain对象,然后添加到Filter的securityFilterChains中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
protected final O doBuild() throws Exception {
synchronized (configurers) {
buildState = BuildState.INITIALIZING;
beforeInit();
init();
buildState = BuildState.CONFIGURING;
beforeConfigure();
configure();
buildState = BuildState.BUILDING;
O result = performBuild();
buildState = BuildState.BUILT;
return result;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
@Override
protected Filter performBuild() throws Exception {
Assert.state(
!securityFilterChainBuilders.isEmpty(),
"At least one SecurityBuilder<? extends SecurityFilterChain> needs to be specified. Typically this done by adding a @Configuration that extends WebSecurityConfigurerAdapter. More advanced users can invoke "
+ WebSecurity.class.getSimpleName()
+ ".addSecurityFilterChainBuilder directly");
int chainSize = ignoredRequests.size() + securityFilterChainBuilders.size();
List<SecurityFilterChain> securityFilterChains = new ArrayList<SecurityFilterChain>(
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);
if (httpFirewall != null) {
filterChainProxy.setFirewall(httpFirewall);
}
filterChainProxy.afterPropertiesSet();
Filter result = filterChainProxy;
if (debugEnabled) {
logger.warn("\n\n"
+ "********************************************************************\n"
+ "********** Security debugging is enabled. *************\n"
+ "********** This may include sensitive information. *************\n"
+ "********** Do not use in a production system! *************\n"
+ "********************************************************************\n\n");
result = new DebugFilter(filterChainProxy);
}
postBuildAction.run();
return result;
}

相关的一些问题

Spring Boot的H2ConsoleAutoConfiguration导致页面标签的权限控制不正常

如果Spring Boot启用了H2 Console的话,由于H2ConsoleAutoConfiguration并没有注解@ConditionalOnMissingBean(WebSecurityConfiguration.class),所以即便应用配置了WebSecurityConfiguration的子类,如果没有显示地把security.basic.enabled设置成false的话,最终还是会导致H2ConsoleSecurityConfiguration配置的套用,H2ConsoleSecurityConfiguration的优先度是SecurityProperties.BASIC_AUTH_ORDER - 10,比较低,反而容易导致WebSecurityFilterSecurityInterceptor被覆盖。

文章目录
  1. 1. 基于源码的流程分析
    1. 1.1. 入口 WebSecurityConfiguration
    2. 1.2. 配置HttpSecurity
    3. 1.3. 构建Filter
  2. 2. 相关的一些问题
    1. 2.1. Spring Boot的H2ConsoleAutoConfiguration导致页面标签的权限控制不正常
Fork me on GitHub