Spring Security配置学习笔记
- AbstractSecurityWebApplicationInitializer用于向web容器中加入DelegatingFilterProxy并制定代理的bean的名字为SpringSecurity专用的
springSecurityFilterChain
:
private void insertSpringSecurityFilterChain(ServletContext servletContext) {
String filterName = DEFAULT_FILTER_NAME;
DelegatingFilterProxy springSecurityFilterChain = new DelegatingFilterProxy(
filterName);
String contextAttribute = getWebApplicationContextAttribute();
if (contextAttribute != null) {
springSecurityFilterChain.setContextAttribute(contextAttribute);
}
registerFilter(servletContext, true, filterName, springSecurityFilterChain);
}
这种方式通常用于纯Spring MVC(非Spring Boot)项目中无web.xml配置Spring Security,类似于在web.xml中加入:
<filter>
<filter-name>springSecurityFilterChain</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>springSecurityFilterChain</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
- 对于Spring Boot,我们没必要用AbstractSecurityWebApplicationInitializer了,因为SecurityFilterAutoConfiguration通过以下方式将DelegatingFilterProxy加入到了web容器:
@Bean
@ConditionalOnBean(name = DEFAULT_FILTER_NAME)
public DelegatingFilterProxyRegistrationBean securityFilterChainRegistration(
SecurityProperties securityProperties) {
DelegatingFilterProxyRegistrationBean registration = new DelegatingFilterProxyRegistrationBean(
DEFAULT_FILTER_NAME);
registration.setOrder(securityProperties.getFilterOrder());
registration.setDispatcherTypes(getDispatcherTypes(securityProperties));
return registration;
}
-
Spring Security采用Servlet的Filter机制,其作用于任何Servlet之前,其中便包括Spring MVC的DispatcherServlet,因此在Spring Security眼中,DispatcherServlet只是一个普通的Servlet而已,和其他Servlet没有什么区别。因此,Spring Security和Spring MVC没有什么关系,可以一并使用,也可以单独将Spring Security用于其他环境,但是Spring Security是需要Spring的IoC容器支持的,因此在非Spring MVC的的Servelt容器中使用时,依然需要配置ContextLoaderListener或者使用AbstractContextLoaderInitializer。
-
Spring Security的配置中有两个重要的配置类,一个是SecuirtyBuilder,这是一个非常泛型化的类,用于构建任何对象,一个是SecurityConfigurator,用于对SecuirtyBuilder进行配置。WebSecurity和HttpSecurity都是SecuirtyBuilder的子类,WebSecurity用于构建出一个FilterChainProxy(一种Filter),而HttpSecurity用于构建出一个DefaultSecurityFilterChain,这些从二者的类定义中可以看出。SecurityConfigurator主要用于配置HttpSecurity,作用是向DefaultSecurityFilterChain注入各种Filter,比如CorsConfigurer会向HttpSecurity中加入CorsFilter。
-
@EnableWebSecurity
通常将在WebSecurityConfigurerAdapter上,并与@Configuration
一并使用,这样一方面可以启用Spring Security,另一方面WebSecurityConfigurerAdapter也可用于配置其他方面,并且WebSecurityConfigurerAdapter本身也是一个bean(加了@Configuration
的类都是一个bean),进而可以被WebSecurityConfiguration自动注入完成配置工作。 -
@EnableWebSecurity
最重要的作用是通过@Import
使WebSecurityConfiguration生效。WebSecurityConfiguration中定义了多个Spring Security的Bean,其中最重要的有个名为springSecurityFilterChain
的bean:
@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();
}
这个名为springSecurityFilterChain
便是Spring Security在启动时通过DelegatingFilterProxy
所要找到的bean。
-
这个名为
springSecurityFilterChain
的bean的真实类型为FilterChainProxy,这个FilterChainProxy也是Spring Security的总入口,FilterChainProxy维护了多个SecurityFilterChain的List,SecurityFilterChain又维护了一个Filter的列表,真实其作用的其实是SecurityFilterChain的Filter列表(比如其中包含有UsernamePasswordAuthenticationFilter和LogoutFilter等)。当请求来时会依次调用各个SecurityFilterChain的matches()方法来确定由哪个SecurityFilterChain处理,当找到第一个之后,后续的SecurityFilterChain便没有机会了,因此放在List前面的SecurityFilterChain会优先起作用,在实际配置时,越精确的路径匹配应该放在越前面。在Spring中可以通过@Order
来确定List中自动注入bean的顺序,也即通过在WebSecurityConfigurerAdapter上加上@Order
,那么在WebSecurityConfiguration中,webSecurityConfigurers列表中的多个WebSecurityConfigurerAdapter便已经按照顺序排列了。默认情况SecurityFilterChain可以处理所有请求,在配置该SecurityFilterChain时,可以使用requestMatcher进行过滤。
关于Spring Security的架构讲解请参考这里和这里。 -
在配置时,有两个主要配置类,一个是WebSecurity,一个是HttpSecurity,整个Spring Security中只有一个WebSecurity,并在WebSecurityConfiguration中完成初始化,而HttpSecruity可以有多个,一个WebSecurityConfigurerAdapter对应创建一个HttpSecurity,进而对应一个SecurityFilterChain。
-
WebSecurityConfiguration创建WebSecurity:
@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;
Object previousConfig = 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 on " + previousConfig + ", so it cannot be used on "
+ config + " too.");
}
previousOrder = order;
previousConfig = config;
}
for (SecurityConfigurer<Filter, WebSecurity> webSecurityConfigurer : webSecurityConfigurers) {
webSecurity.apply(webSecurityConfigurer);
}
this.webSecurityConfigurers = webSecurityConfigurers;
}
- WebSecurityConfigurerAdapter创建HttpSecurity:
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);
authenticationBuilder.authenticationEventPublisher(eventPublisher);
Map<Class<? extends Object>, Object> sharedObjects = createSharedObjects();
http = new HttpSecurity(objectPostProcessor, authenticationBuilder,
sharedObjects);
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<>()).and()
.logout();
// @formatter:on
ClassLoader classLoader = this.context.getClassLoader();
List<AbstractHttpConfigurer> defaultHttpConfigurers =
SpringFactoriesLoader.loadFactories(AbstractHttpConfigurer.class, classLoader);
for (AbstractHttpConfigurer configurer : defaultHttpConfigurers) {
http.apply(configurer);
}
}
configure(http);
return http;
}
- WebSeurity主要用于配置需要忽略的请求:
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring()
.antMatchers("/bi/email-login/**");
}
- HttpSecurity主要用于配置各种其作用的Filter,因此在配置Spring Security时,我们主要配置的是HttpSecrity,比如:
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf()
.disable()
.authorizeRequests()
.anyRequest()
.authenticated();
}
-
WebSecurity用于配置FilterChainProxy,整个Spring Security中只有一个FilterChainProxy,也即只有一个WebSeurity配置,WebSecurity创建于@EnableWebSecurity的入口配置WebSecurityConfiguration中。
-
如果不想Spring Security应用于某个路径,可以通过WebSecurity的ignoring()进行配置,实际上WebSecurity会根据该ignoring()配置创建一个SecurityFilterChain,只是该SecurityFilterChain没有任何Filter而已。
-
HttpSecurity用于一对一配置SecurityFilterChain,一个FilterChainProxy可以包含多个SecurityFilterChain,因此Spring Security在配置时可以有多个HttpSecurity。HttpSecurity创建于WebSecurityConfigurerAdapter中,也即一个WebSecurityConfigurerAdapter对应配置一个HttpSecurity,进而对应配置一个SecurityFilterChain。
-
WebSecurityConfiguration在初始化的时候会通过@Autowire自动注入所实现了WebSecurityConfigurerAdapter接口bean,所有这些WebSecurityConfigurerAdapter可以配置相同的一个WebSecurity,并且可以配置自己的HttpSecurity。
-
Spring Boot通过SecurityAutoConfiguration配置了一个空的WebSecurityConfigurerAdapter,然后通过UserDetailsServiceAutoConfiguration配置了一个in memory的用户,用户名user,随机密码,并由此创建一个InMemoryUserDetailsManager类型的Bean。InMemoryUserDetailsManager实现了UserDetailsService,该InMemoryUserDetailsManager会被Spring Security的AuthenticationConfiguration通过globalAuthConfigurers找到,即InitializeUserDetailsManagerConfigurer/InitializeUserDetailsBeanManagerConfigurer会在IoC容器中查找UserDetailsService类型bean,并将其用于构建全局的AuthenticationManager。由于InitializeUserDetailsBeanManagerConfigurer实现了GlobalAuthenticationConfigurerAdapter,因此在AuthenticationConfiguration中会被自动找到:
@Autowired(required = false)
public void setGlobalAuthenticationConfigurers(
List<GlobalAuthenticationConfigurerAdapter> configurers) throws Exception {
Collections.sort(configurers, AnnotationAwareOrderComparator.INSTANCE);
this.globalAuthConfigurers = configurers;
}
- Spring Security通过FilterComparator对SecurityFilterChain中的多个Filter进行排序,可以看到Filter的默认顺序都是写死的。
FilterComparator() {
Step order = new Step(INITIAL_ORDER, ORDER_STEP);
put(ChannelProcessingFilter.class, order.next());
put(ConcurrentSessionFilter.class, order.next());
put(WebAsyncManagerIntegrationFilter.class, order.next());
put(SecurityContextPersistenceFilter.class, order.next());
put(HeaderWriterFilter.class, order.next());
put(CorsFilter.class, order.next());
put(CsrfFilter.class, order.next());
put(LogoutFilter.class, order.next());
filterToOrder.put(
"org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestRedirectFilter",
order.next());
put(X509AuthenticationFilter.class, order.next());
put(AbstractPreAuthenticatedProcessingFilter.class, order.next());
filterToOrder.put("org.springframework.security.cas.web.CasAuthenticationFilter",
order.next());
filterToOrder.put(
"org.springframework.security.oauth2.client.web.OAuth2LoginAuthenticationFilter",
order.next());
put(UsernamePasswordAuthenticationFilter.class, order.next());
put(ConcurrentSessionFilter.class, order.next());
filterToOrder.put(
"org.springframework.security.openid.OpenIDAuthenticationFilter", order.next());
put(DefaultLoginPageGeneratingFilter.class, order.next());
put(DefaultLogoutPageGeneratingFilter.class, order.next());
put(ConcurrentSessionFilter.class, order.next());
put(DigestAuthenticationFilter.class, order.next());
filterToOrder.put(
"org.springframework.security.oauth2.server.resource.web.BearerTokenAuthenticationFilter", order.next());
put(BasicAuthenticationFilter.class, order.next());
put(RequestCacheAwareFilter.class, order.next());
put(SecurityContextHolderAwareRequestFilter.class, order.next());
put(JaasApiIntegrationFilter.class, order.next());
put(RememberMeAuthenticationFilter.class, order.next());
put(AnonymousAuthenticationFilter.class, order.next());
filterToOrder.put(
"org.springframework.security.oauth2.client.web.OAuth2AuthorizationCodeGrantFilter",
order.next());
put(SessionManagementFilter.class, order.next());
put(ExceptionTranslationFilter.class, order.next());
put(FilterSecurityInterceptor.class, order.next());
put(SwitchUserFilter.class, order.next());
}
- Spring Security的FilterChainProxy在默认情况下的order为
OrderedFilter.REQUEST_WRAPPER_FILTER_MAX_ORDER - 100
,这是一个很靠前的排序了。可以设置该Filter的顺序号,比如:
security.filter-order=5
对于其他自定义的Bean,可以通过FilterRegistrationBean.setOrder()进行排序,或者通过@Order
:
@Order(Ordered.LOWEST_PRECEDENCE -1)
@Component
public class ABCFilter implements Filter {
------
}