1. 导语
在Spring cloud体系中设计OAuth2应用时,当我们使用
@EnableResourceServer
注解把某个spring boot应用声明为一个OAuth2的资源服务器时,spring cloud将会创建一系列的web安全过滤器链。接下来,我将带大家一起将分析spring cloud的源码,分析过滤器链路的工作原理。
2. spring cloud的web安全配置
spring boot启动时,在WebSecurityConfiguration中加载所有的web安全配置,并根据Order进行排序:
package org.springframework.security.config.annotation.web.configuration;
@Configuration
public class WebSecurityConfiguration implements ImportAware, BeanClassLoaderAware {
// 注意:webSecurityConfigurers参数通过注解的方式注入
@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);
}
// 根据@Order顺序进行排序
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;
}
}
注意:webSecurityConfigurers参数通过注解的方式注入:
@Value("#{@autowiredWebSecurityConfigurersIgnoreParents.getWebSecurityConfigurers()}") List<SecurityConfigurer<Filter, WebSecurity>> webSecurityConfigurers
即在spring容器里查找autowiredWebSecurityConfigurersIgnoreParents
的实例并调用其getWebSecurityConfigurer()
方法。
AutowiredWebSecurityConfigurersIgnoreParents
类的作用是从spring容器中找到WebSecurityConfigure
接口的所有实例:
final class AutowiredWebSecurityConfigurersIgnoreParents {
private final ConfigurableListableBeanFactory beanFactory;
public AutowiredWebSecurityConfigurersIgnoreParents(
ConfigurableListableBeanFactory beanFactory) {
Assert.notNull(beanFactory, "beanFactory cannot be null");
this.beanFactory = beanFactory;
}
// 从spring容器中找到WebSecurityConfigurer接口的所有实例
@SuppressWarnings({ "rawtypes", "unchecked" })
public List<SecurityConfigurer<Filter, WebSecurity>> getWebSecurityConfigurers() {
List<SecurityConfigurer<Filter, WebSecurity>> webSecurityConfigurers = new ArrayList<SecurityConfigurer<Filter, WebSecurity>>();
Map<String, WebSecurityConfigurer> beansOfType = beanFactory
.getBeansOfType(WebSecurityConfigurer.class);
for (Entry<String, WebSecurityConfigurer> entry : beansOfType.entrySet()) {
webSecurityConfigurers.add(entry.getValue());
}
return webSecurityConfigurers;
}
}
找到的实例包括:
类 | 顺序 (Order) | 说明 |
---|---|---|
IgnoredPathsWebSecurityConfigurerAdapter | -2147483648:Ordered.HIGHEST_PRECEDENCE | 白名单 |
ManagementWebSecurityConfigurerAdapter | 2147483637:Ordered.LOWEST_PRECEDENCE - 10 | spring自身的管理/监控端点过滤器链 |
ResourceServerConfiguration | 2147483639:Ordered.LOWEST_PRECEDENCE - 8 | 资源服务器相关的过滤器链 |
ApplicationWebSecurityConfigurerAdapter | 2147483642:Ordered.LOWEST_PRECEDENCE - 5 | 默认的web安全过滤器链 |
其中:
ResourceServerConfiguration的Order默认为3,但在OAuth2的资源服务器中,运行时将会被覆盖为其他值,具体值在ResourceServerProperties
类的属性filterOrder
中定义:
@ConfigurationProperties(prefix = "security.oauth2.resource")
public class ResourceServerProperties implements Validator, BeanFactoryAware {
/**
* The order of the filter chain used to authenticate tokens. Default puts it after
* the actuator endpoints and before the default HTTP basic filter chain (catchall).
*/
private int filterOrder = SecurityProperties.ACCESS_OVERRIDE_ORDER - 1;
3. 安全配置的初始化
接下来,在AbstractConfiguredSecurityBuilder
类中对安全配置类进行初始化:
package org.springframework.security.config.annotation;
public abstract class AbstractConfiguredSecurityBuilder<O, B extends SecurityBuilder<O>>
extends AbstractSecurityBuilder<O> {
private void init() throws Exception {
// configurers里面初始化了4个配置
Collection<SecurityConfigurer<O, B>> configurers = getConfigurers();
for (SecurityConfigurer<O, B> configurer : configurers) {
configurer.init((B) this);
}
for (SecurityConfigurer<O, B> configurer : configurersAddedInInitializing) {
configurer.init((B) this);
}
}
}
configurers里面包含以下4个web安全配置类,即上面在spring容器中查找到的4个安全配置类实例:
[0] SpringBootWebSecurityConfiguration$IgnoredPathsWebSecurityConfigurerAdapter (id=7391)
[1] ManagementWebSecurityAutoConfiguration$ManagementWebSecurityConfigurerAdapter$$EnhancerBySpringCGLIB$$b7d4741a (id=105)
[2] ResourceServerConfiguration$$EnhancerBySpringCGLIB$$dae34c39 (id=7392)
[3] SpringBootWebSecurityConfiguration$ApplicationWebSecurityConfigurerAdapter$$EnhancerBySpringCGLIB$$39896683 (id=7393)
ResourceServerConfiguration的配置
package org.springframework.security.oauth2.config.annotation.web.configuration;
@Configuration
public class ResourceServerConfiguration extends WebSecurityConfigurerAdapter implements Ordered {
@Override
protected void configure(HttpSecurity http) throws Exception {
ResourceServerSecurityConfigurer resources = new ResourceServerSecurityConfigurer();
ResourceServerTokenServices services = resolveTokenServices();
if (services != null) {
resources.tokenServices(services);
}
else {
if (tokenStore != null) {
resources.tokenStore(tokenStore);
}
else if (endpoints != null) {
resources.tokenStore(endpoints.getEndpointsConfigurer().getTokenStore());
}
}
if (eventPublisher != null) {
resources.eventPublisher(eventPublisher);
}
for (ResourceServerConfigurer configurer : configurers) {
configurer.configure(resources);
}
// @formatter:off
http.authenticationProvider(new AnonymousAuthenticationProvider("default"))
// N.B. exceptionHandling is duplicated in resources.configure() so that
// it works
.exceptionHandling()
.accessDeniedHandler(resources.getAccessDeniedHandler()).and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.csrf().disable();
// @formatter:on
http.apply(resources);
if (endpoints != null) {
// Assume we are in an Authorization Server
http.requestMatcher(new NotOAuthRequestMatcher(endpoints.oauth2EndpointHandlerMapping()));
}
for (ResourceServerConfigurer configurer : configurers) {
// Delegates can add authorizeRequests() here
configurer.configure(http);
}
if (configurers.isEmpty()) {
// Add anyRequest() last as a fall back. Spring Security would
// replace an existing anyRequest() matcher with this one, so to
// avoid that we only add it if the user hasn't configured anything.
http.authorizeRequests().anyRequest().authenticated();
}
}
}
4. 将安全配置类转化为安全过滤器链
将安全配置类转化为安全过滤器链的这个工作是由WebSecurity
类来完成的,我们来看下相关代码:
package org.springframework.security.config.annotation.web.builders;
public final class WebSecurity extends
AbstractConfiguredSecurityBuilder<Filter, WebSecurity> implements
SecurityBuilder<Filter>, ApplicationContextAware {
@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);
// 一、针对白名单url创建一条空的过滤器链(Chain A)
for (RequestMatcher ignoredRequest : ignoredRequests) {
securityFilterChains.add(new DefaultSecurityFilterChain(ignoredRequest));
}
// 二、安全相关的过滤器链,可能为多条,这里是3条。
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 cloud一共创建了4条过滤器链。
其中:
4.1 Chain A:配置了一个静态文件url的白名单
对应着前面的白名单安全配置类
IgnoredPathsWebSecurityConfigurerAdapter
具体的配置在:
package org.springframework.boot.autoconfigure.security;
public class SpringBootWebSecurityConfiguration {
private static List<String> DEFAULT_IGNORED = Arrays.asList("/css/**", "/js/**",
"/images/**", "/webjars/**", "/**/favicon.ico");
// ...
private class DefaultIgnoredRequestCustomizer implements IgnoredRequestCustomizer {
// 这里定义了一个魔法
private List<String> getIgnored(SecurityProperties security) {
List<String> ignored = new ArrayList<String>(security.getIgnored());
if (ignored.isEmpty()) {
ignored.addAll(DEFAULT_IGNORED);
}
else if (ignored.contains("none")) {
ignored.remove("none");
}
return ignored;
}
}
}
我们可以看到SpringBootWebSecurityConfiguration
定义了一个默认的uri白名单,里面定义了一些静态文件的uri:
- "/css/**"
- "/js/**",
- "/images/**"
- "/webjars/**"
- "/**/favicon.ico"
因此,对匹配这些uri的http请求,将会跳过后续所有的过滤器链。
那么,接下来,我们可能会有疑问,如果我想对这些uri进行定制化怎么办?
答案就在getIgnored()
方法,它定义了一个魔法
,因此允许我们对白名单进行定制化。有两种方法:
1、在application.yml中配置security.ignored属性
security:
ignored: none
如果security.ignored=none,则忽略上面的白名单。
当然,你也可以做个性化的配置,如:
security:
ignored: /css/**,/js/**,/images/**,/webjars/**,/**/favicon.ico,/static/**
2、或者通过代码实现
@Component
public class CustomSecurityProperties extends SecurityProperties {
public CustomSecurityProperties() {
// the default list is empty
List<String> ignoredPaths = getIgnored();
ignoredPaths.add("none");
}
}
4.2 安全相关的过滤器链,这里创建了3条
对应前面的其他3个过滤器安全配置类:
- ManagementWebSecurityConfigurerAdapter,spring自身的管理/监控端点过滤器链
- ResourceServerConfiguration,资源服务器相关的过滤器链
- ApplicationWebSecurityConfigurerAdapter,默认的web安全过滤器链
1. ManagementWebSecurityConfigurerAdapter
ManagementWebSecurityConfigurerAdapter
是spring自身的管理/监控端点过滤器链。包括以下端点:
OrRequestMatcher
[requestMatchers=
[
Ant [pattern='/pause'],
Ant [pattern='/pause/**'],
Ant [pattern='/pause.*'],
Ant [pattern='/pause/'],
Ant [pattern='/env'],
Ant [pattern='/env/**'],
Ant [pattern='/env.*'],
Ant [pattern='/env/'],
Ant [pattern='/features'],
Ant [pattern='/features/**'],
Ant [pattern='/features.*'],
Ant [pattern='/features/'],
Ant [pattern='/loggers'],
Ant [pattern='/loggers/**'],
Ant [pattern='/loggers.*'],
Ant [pattern='/loggers/'],
Ant [pattern='/archaius'],
Ant [pattern='/archaius/**'],
Ant [pattern='/archaius.*'],
Ant [pattern='/archaius/'],
Ant [pattern='/autoconfig'],
Ant [pattern='/autoconfig/**'],
Ant [pattern='/autoconfig.*'],
Ant [pattern='/autoconfig/'],
Ant [pattern='/mappings'],
Ant [pattern='/mappings/**'],
Ant [pattern='/mappings.*'],
Ant [pattern='/mappings/'],
Ant [pattern='/refresh'],
Ant [pattern='/refresh/**'],
Ant [pattern='/refresh.*'],
Ant [pattern='/refresh/'],
Ant [pattern='/restart'],
Ant [pattern='/restart/**'],
Ant [pattern='/restart.*'],
Ant [pattern='/restart/'],
Ant [pattern='/info'],
Ant [pattern='/info/**'],
Ant [pattern='/info.*'],
Ant [pattern='/info/'],
Ant [pattern='/resume'],
Ant [pattern='/resume/**'],
Ant [pattern='/resume.*'],
Ant [pattern='/resume/'],
Ant [pattern='/metrics'],
Ant [pattern='/metrics/**'],
Ant [pattern='/metrics.*'],
Ant [pattern='/metrics/'],
Ant [pattern='/beans'],
Ant [pattern='/beans/**'],
Ant [pattern='/beans.*'],
Ant [pattern='/beans/'],
Ant [pattern='/trace'],
Ant [pattern='/trace/**'],
Ant [pattern='/trace.*'],
Ant [pattern='/trace/'],
Ant [pattern='/auditevents'],
Ant [pattern='/auditevents/**'],
Ant [pattern='/auditevents.*'],
Ant [pattern='/auditevents/'],
Ant [pattern='/configprops'],
Ant [pattern='/configprops/**'],
Ant [pattern='/configprops.*'],
Ant [pattern='/configprops/'],
Ant [pattern='/service-registry'],
Ant [pattern='/service-registry/**'],
Ant [pattern='/service-registry.*'],
Ant [pattern='/service-registry/'],
Ant [pattern='/dump'],
Ant [pattern='/dump/**'],
Ant [pattern='/dump.*'],
Ant [pattern='/dump/'],
Ant [pattern='/health'],
Ant [pattern='/health/**'],
Ant [pattern='/health.*'],
Ant [pattern='/health/'],
Ant [pattern='/heapdump'],
Ant [pattern='/heapdump/**'],
Ant [pattern='/heapdump.*'],
Ant [pattern='/heapdump/']
]
]
2. ResourceServerConfiguration
ResourceServerConfiguration
对应着我们自定义的资源服务器的配置类,我们可以在里面配置我们自定义的过滤器。例如:
/**
* 自定义资源服务器配置
*
* @author 大浪滔滔
*
*/
@Configuration
@EnableConfigurationProperties(SecuritySettings.class)
public class DefaultResourceServerConfiguration extends ResourceServerConfigurerAdapter {
@Override
public void configure(final HttpSecurity http) throws Exception {
// 自定义的过滤器
http.addFilterBefore(threadLocalFilter(), WebAsyncManagerIntegrationFilter.class);
http.addFilterAfter(accessFilter(), ThreadLocalFilter.class);
http.addFilterAfter(serviceAuthFilter, ThreadLocalFilter.class);
// ...
}
}
在这个过滤器链中包含以下过滤器:
序号 | 过滤器 |
---|---|
[0] | ThreadLocalFilter (id=382),安全上下文过滤器。 |
[1] | WebAsyncManagerIntegrationFilter (id=383) |
[2] | AccessFilter (id=384),日志过滤器。 |
[3] | ServiceAuthFilter (id=385),微服务token认证过滤器。 |
[4] | SecurityContextPersistenceFilter (id=387) |
[5] | HeaderWriterFilter (id=388) |
[6] | LogoutFilter (id=389) |
[7] | OAuth2AuthenticationProcessingFilter (id=390) |
[8] | RequestCacheAwareFilter (id=391) |
[9] | SecurityContextHolderAwareRequestFilter (id=392) |
[10] | AnonymousAuthenticationFilter (id=393) |
[11] | SessionManagementFilter (id=394) |
[12] | ExceptionTranslationFilter (id=395) |
[13] | FilterSecurityInterceptor (id=396) |
其中,ThreadLocalFilter、AccessFilter和ServiceAuthFilter是我们自定义的过滤器。
其他过滤器是spring自动生成的,见以下代码:
package org.springframework.security.config.annotation.web.configuration;
@Order(100)
public abstract class WebSecurityConfigurerAdapter implements
WebSecurityConfigurer<WebSecurity> {
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);
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<HttpSecurity>()).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;
}
}
3. ApplicationWebSecurityConfigurerAdapter
ApplicationWebSecurityConfigurerAdapter
是spring默认的web安全过滤器链,它对uri的匹配规则是
OrRequestMatcher [requestMatchers=[Ant [pattern='/**']]]
即匹配所有的http请求。
它包括以下过滤器:
序号 | 过滤器 |
---|---|
[0] | WebAsyncManagerIntegrationFilter (id=370) |
[1] | SecurityContextPersistenceFilter (id=371) |
[2] | HeaderWriterFilter (id=372) |
[3] | LogoutFilter (id=373) |
[4] | BasicAuthenticationFilter (id=374) |
[5] | RequestCacheAwareFilter (id=375) |
[6] | SecurityContextHolderAwareRequestFilter (id=376) |
[7] | AnonymousAuthenticationFilter (id=377) |
[8] | SessionManagementFilter (id=378) |
[9] | ExceptionTranslationFilter (id=379) |
[10] | FilterSecurityInterceptor (id=380) |
5. 过滤器链的选择
在前面的分析中,spring创建了4条过滤器链,那么,是否每条链都会被执行呢?
答案是否定的,这4条过滤器链每条都有uri的匹配规则,spring将会根据http请求的uri按顺序去匹配,第1条符合条件的过滤器链将会被选中并执行。
相关的代码如下:
package org.springframework.security.web;
public class FilterChainProxy extends GenericFilterBean {
/**
* Returns the first filter chain matching the supplied URL.
*
* @param request the request to match
* @return an ordered array of Filters defining the filter chain
*/
private List<Filter> getFilters(HttpServletRequest request) {
for (SecurityFilterChain chain : filterChains) {
if (chain.matches(request)) {
return chain.getFilters();
}
}
return null;
}
6. 过滤器的执行顺序
HttpSecurity
类定义了一个安全过滤器排序器FilterComparator
package org.springframework.security.config.annotation.web.builders;
public final class HttpSecurity extends
AbstractConfiguredSecurityBuilder<DefaultSecurityFilterChain, HttpSecurity>
implements SecurityBuilder<DefaultSecurityFilterChain>,
HttpSecurityBuilder<HttpSecurity> {
// 过滤器排序器
private FilterComparator comparator = new FilterComparator();
}
FilterComparator
类定义一些spring自带过滤器的默认顺序:
package org.springframework.security.config.annotation.web.builders;
final class FilterComparator implements Comparator<Filter>, Serializable {
private static final int STEP = 100;
private Map<String, Integer> filterToOrder = new HashMap<String, Integer>();
FilterComparator() {
int order = 100;
put(ChannelProcessingFilter.class, order);
order += STEP;
put(ConcurrentSessionFilter.class, order);
order += STEP;
put(WebAsyncManagerIntegrationFilter.class, order);
order += STEP;
put(SecurityContextPersistenceFilter.class, order);
order += STEP;
put(HeaderWriterFilter.class, order);
order += STEP;
put(CorsFilter.class, order);
order += STEP;
put(CsrfFilter.class, order);
order += STEP;
put(LogoutFilter.class, order);
order += STEP;
put(X509AuthenticationFilter.class, order);
order += STEP;
put(AbstractPreAuthenticatedProcessingFilter.class, order);
order += STEP;
filterToOrder.put("org.springframework.security.cas.web.CasAuthenticationFilter",
order);
order += STEP;
put(UsernamePasswordAuthenticationFilter.class, order);
order += STEP;
put(ConcurrentSessionFilter.class, order);
order += STEP;
filterToOrder.put(
"org.springframework.security.openid.OpenIDAuthenticationFilter", order);
order += STEP;
put(DefaultLoginPageGeneratingFilter.class, order);
order += STEP;
put(ConcurrentSessionFilter.class, order);
order += STEP;
put(DigestAuthenticationFilter.class, order);
order += STEP;
put(BasicAuthenticationFilter.class, order);
order += STEP;
put(RequestCacheAwareFilter.class, order);
order += STEP;
put(SecurityContextHolderAwareRequestFilter.class, order);
order += STEP;
put(JaasApiIntegrationFilter.class, order);
order += STEP;
put(RememberMeAuthenticationFilter.class, order);
order += STEP;
put(AnonymousAuthenticationFilter.class, order);
order += STEP;
put(SessionManagementFilter.class, order);
order += STEP;
put(ExceptionTranslationFilter.class, order);
order += STEP;
put(FilterSecurityInterceptor.class, order);
order += STEP;
put(SwitchUserFilter.class, order);
}
}
过滤器名称 | 过滤器类 | 顺序 | Order值 | 说明 |
---|---|---|---|---|
MetricsFilter | @Order(Ordered.HIGHEST_PRECEDENCE) | -2147483648 | ||
OrderedCharacterEncodingFilter | -2147483648 | |||
OrderedHiddenHttpMethodFilter | -10000 | |||
OrderedHttpPutFormContentFilter | -9900 | |||
OrderedRequestContextFilter | -105 | |||
ThreadLocalFilter | -104 | 自定义过滤器 | ||
ServiceAuthFilter | -103 | 自定义过滤器 | ||
AccessFilter | -102 | 自定义过滤器 | ||
springSecurityFilterChain | DelegatingFilterProxyRegistrationBean | -100 | ||
webRequestLoggingFilter | WebRequestTraceFilter | 2147483637 |