最近在整合微服务OAuth 2认证过程中,它是基于Spring Security之上,而本人对Spring Security架构原理并不太熟悉,导致很多配置搞不太清楚,遂咬牙啃完了Spring Security核心源码,花了差不多一星期,总体上来说,其代码确实比较晦涩,之前在学习Apache Shiro框架之前也曾经在相关论坛里了解过,相比Spring Security,Apache Shiro真的是相当轻量,代码研读起来容易很多,而Spring Security类继承结构复杂,大量使用了其所谓Builder和Configuer模式,其代码跟踪过程很痛苦,遂记录下,分享给有需要的人,由于本人能力有限,在文章中有不对之处,还请各位执教,在此谢谢各位了。
本人研读的Spring Security版本为: 5.1.4.RELEASE
Spring Security在3.2版本之后支持Java Configuration,即:通过Java编码形式配置Spring Security,可不再依赖XML文件配置,本文采用Java Configuration方式。
在Spring Security官方文档中有一个最简配置例子:
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.*; import org.springframework.security.config.annotation.authentication.builders.*; import org.springframework.security.config.annotation.web.configuration.*; @Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { auth .inMemoryAuthentication() .withUser("user").password("password").roles("USER"); } }
我们先不要看其它内容,先关注注解 @EnableWebSecurity
,它是初始化Spring Security的入口,打开其源码如下:
@Retention(value = java.lang.annotation.RetentionPolicy.RUNTIME) @Target(value = { java.lang.annotation.ElementType.TYPE }) @Documented @Import({ WebSecurityConfiguration.class, SpringWebMvcImportSelector.class, OAuth2ImportSelector.class }) @EnableGlobalAuthentication @Configuration public @interface EnableWebSecurity { /** * Controls debugging support for Spring Security. Default is false. * @return if true, enables debug support with Spring Security */ boolean debug() default false; }
该注解类通过 @Configuration
和 @Import
配合使用引入了一个配置类( WebSecurityConfiguration
)和两个ImportSelector( SpringWebMvcImportSelector
, OAuth2ImportSelector
),我们重点关注下 WebSecurityConfiguration
,它是Spring Security的核心,正是它构建初始化了所有的Bean实例和相关配置,下面我们详细分析下。
打开 WebSecurityConfiguration
源码,发现它被 @Configuration
标记,说明它是配置类,
@Configuration public class WebSecurityConfiguration implements ImportAware, BeanClassLoaderAware
该类中最重要的工作就是实例并注册 FilterChainProxy
,也就是我们在以前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>
该过滤器负责拦截请求,并把请求通过一定的匹配规则(通过RequestMatcher匹配实现)路由(或者Delegate)到具体的 SecurityFilterChain
,源码如下:
/** * Creates the Spring Security Filter Chain * @return the {@link Filter} that represents the security filter chain * @throws Exception */ @Bean(name = AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME) public Filter springSecurityFilterChain() throws Exception { boolean hasConfigurers = webSecurityConfigurers != null && SecurityConfigurers.isEmpty(); if (!hasConfigurers) { WebSecurityConfigurerAdapter adapter = objectObjectPostProcessor .postProcess(new WebSecurityConfigurerAdapter() { }); webSecurity.apply(adapter); } return webSecurity.build(); }
@Bean
注解 name
属性值 AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME
就是XML中定义的 springSecurityFilterChain
。
从源码中知道过滤器通过最后的 webSecurity.build()
创建, webSecurity
的类型为: WebSecurity
,它在 setFilterChainProxySecurityConfigurer
方法中优先被创建了:
/** * Sets the {@code <SecurityConfigurer<FilterChainProxy, WebSecurityBuilder>} * instances used to create the web configuration. * * @param objectPostProcessor the {@link ObjectPostProcessor} used to create a * {@link WebSecurity} instance * @param webSecurityConfigurers the * {@code <SecurityConfigurer<FilterChainProxy, WebSecurityBuilder>} instances used to * create the web configuration * @throws Exception */ @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; }
从代码中可以看到,它是直接被new出来的:
webSecurity = objectPostProcessor .postProcess(new WebSecurity(objectPostProcessor));
setFilterChainProxySecurityConfigurer方法参数中需要被注入两个对象: objectPostProcessor
和 webSecurityConfigurers
, objectPostProcessor
是在 ObjectPostProcessorConfiguration
配置类中注册的,而 webSecurityConfigurers
则是使用了 @Value
注解方式,注解内容为: #{@autowiredWebSecurityConfigurersIgnoreParents.getWebSecurityConfigurers()}
,通过源码了解, autowiredWebSecurityConfigurersIgnoreParents
是在本类中被注册:
@Bean public static AutowiredWebSecurityConfigurersIgnoreParents autowiredWebSecurityConfigurersIgnoreParents( ConfigurableListableBeanFactory beanFactory) { return new AutowiredWebSecurityConfigurersIgnoreParents(beanFactory); }
在AutowiredWebSecurityConfigurersIgnoreParents中定义了方法: getWebSecurityConfigurers
:
@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; }
它通过BeanFactory获取了类型为 WebSecurityConfigurer
的Bean实例列表。回到 WebSecurityConfiguration
类中的 setFilterChainProxySecurityConfigurer
方法,它把 WebSecurityConfigurer
列表设置到了 WebSecurity
中,源码如下:
for (SecurityConfigurer<Filter, WebSecurity> webSecurityConfigurer : webSecurityConfigurers) { webSecurity.apply(webSecurityConfigurer); }
通过 apply
方法,apply方法其实就是 webSecurityConfigurer
放入 webSecurity
维护的 configurers
属性中, configurers
是个 LinkedHashMap
,源码如下:
/** * Applies a {@link SecurityConfigurer} to this {@link SecurityBuilder} overriding any * {@link SecurityConfigurer} of the exact same class. Note that object hierarchies * are not considered. * * @param configurer * @return the {@link SecurityConfigurerAdapter} for further customizations * @throws Exception */ public <C extends SecurityConfigurer<O, B>> C apply(C configurer) throws Exception { add(configurer); return configurer; }
其中代码 add(configurer)
就是将这些 webSecurityConfigurer
添加到 webSecurity
的 configurers
属性中。
现在 webSecurity
的初始化工作已经完成,现在回到 springSecurityFilterChain
方法中,它首先检查当前是否配置了 webSecurityConfigurer
,如果没有的会默认设置一个,并且调用上面提到的 apply
方法,源码如下:
boolean hasConfigurers = webSecurityConfigurers != null && SecurityConfigurers.isEmpty(); if (!hasConfigurers) { WebSecurityConfigurerAdapter adapter = objectObjectPostProcessor .postProcess(new WebSecurityConfigurerAdapter() { }); webSecurity.apply(adapter); }
如果已经存在配置了 webSecurityConfigurer
,则调用 webSecurity.build()
进行构建。
在进入 build
方法之前,首先简单介绍下WebSecurity的继承结构,
它实现了 SecurityBuilder
接口,继承自 AbstractConfiguredSecurityBuilder
, AbstractConfiguredSecurityBuilder
继承自 AbstractSecurityBuilder
, AbstractSecurityBuilder
实现了 SecurityBuilder
,其中 AbstractConfiguredSecurityBuilder
实现了通过自定义 SecurityConfigurer
类来配置 SecurityBuilder
,上面提到的 apply(SecurityConfigurer configurer)
就是在该类中实现的,它把configurer保存在它维护的 LinkedHashMap<Class<? extends SecurityConfigurer<O, B>>, List<SecurityConfigurer<O, B>>> configurers = new LinkedHashMap<Class<? extends SecurityConfigurer<O, B>>, List<SecurityConfigurer<O, B>>>()
中。
调用 webSecurity.build()
后,首先调用的父类 AbstractSecurityBuilder
中的 build
方法:
public final O build() throws Exception { if (this.building.compareAndSet(false, true)) { this.object = doBuild(); return this.object; } throw new AlreadyBuiltException("This object has already been built"); }
然后调用 doBuild()
, doBuild()
在子类中实现, AbstractConfiguredSecurityBuilder
实现了该方法:
@Override 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; } }
在这里重点关注 init()
、 configure()
和 performBuild()
,下面逐个分析它们的作用。
init()
方法在 AbstractConfiguredSecurityBuilder
实现:
private void init() throws Exception { 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); } }
它的工作是迭代调用所有配置的 SecurityConfigrer
的 init
方法,在这里其实是它的子类 WebSecurityConfigurer
,因为之前获取时指定的类型就是 WebSecurityConfigurer
,在上文中提到 AutowiredWebSecurityConfigurersIgnoreParents.getWebSecurityConfigurers()
中:
Map<String, WebSecurityConfigurer> beansOfType = beanFactory.getBeansOfType(WebSecurityConfigurer.class);
而实现了 WebSecurityConfigurer
接口的就是 WebSecurityConfigurerAdapter
, WebSecurityConfigurerAdapter.init()
源码如下:
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); } }); }
它只要完成两件重要的事情:
HttpSecurity
对象; HttpSecurity
对象添加至 WebSecurity
的 securityFilterChainBuilders
列表中; 初始化 HttpSecurity
对象在 getHttp()
方法中实现:
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; }
从代码中可以了解, HttpSecurity
是直接被new出来的,在创建 HttpSecurity
之前,首先初始化了 AuthenticationManagerBuilder
对象,这里有段代码很重要就是: AuthenticationManager authenticationManager = authenticationManager();
,它创建 AuthenticationManager
实例,打开 authenticationManager()
方法:
protected AuthenticationManager authenticationManager() throws Exception { if (!authenticationManagerInitialized) { configure(localConfigureAuthenticationBldr); if (disableLocalConfigureAuthenticationBldr) { authenticationManager = authenticationConfiguration .getAuthenticationManager(); } else { authenticationManager = localConfigureAuthenticationBldr.build(); } authenticationManagerInitialized = true; } return authenticationManager; }
在初始化时,它会调用 configure(localConfigureAuthenticationBldr);
,默认的实现是:
protected void configure(AuthenticationManagerBuilder auth) throws Exception { this.disableLocalConfigureAuthenticationBldr = true; }
【1、个性化配置入口之 configure(AuthenticationManagerBuilder auth)
】
我们可以通过继承 WebSecurityConfigurerAdapter
并重写该方法来个性化配置 AuthenticationManager
。
构建完 authenticationManager
实例后,将它设置为 authenticationBuilder
的父认证管理器:
authenticationBuilder.parentAuthenticationManager(authenticationManager);
并将该 authenticationBuilder
传入 HttpSecurity
构造器构建 HttpSecurity
实例。
构建完 HttpSecurity
实例后,默认情况下会添加默认的拦截其配置:
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();
最后调用 configure(http);
,这又是一个可个性化的配置入口,它的默认实现是:
protected void configure(HttpSecurity http) throws Exception { logger.debug("Using default configure(HttpSecurity). If subclassed this will potentially override subclass configure(HttpSecurity)."); http .authorizeRequests() .anyRequest().authenticated() .and() .formLogin().and() .httpBasic(); }
默认的配置是拦截所有的请求需要认证之后才能访问,如果没有认证,会自动生成一个认证表单要求输入用户名和密码。
【2、个性化配置入口之 configure(HttpSecurity http)
】
我们可以通过继承 WebSecurityConfigurerAdapter
并重写该方法来个性化配置 HttpSecurity
。
OK,目前为止 HttpSecurity
已经被初始化,接下去需要设置 HttpSecurity
对象添加至 WebSecurity
的 securityFilterChainBuilders
列表中:
web.addSecurityFilterChainBuilder(http).postBuildAction(new Runnable() { public void run() { FilterSecurityInterceptor securityInterceptor = http .getSharedObject(FilterSecurityInterceptor.class); web.securityInterceptor(securityInterceptor); } });
打开 HttpSecurity
类结构,和WebSecurity一样,它也实现了 SecurityBuilder
接口,同样继承自 AbstractConfiguredSecurityBuilder
。
当所有的 WebSecurityConfigurer
的 init
方法被调用之后, webSecurity.init()
工作就结束了。
接下去调用了 webSecurity.configure()
,该方法同样是在 AbstractConfiguredSecurityBuilder
中实现的:
private void configure() throws Exception { Collection<SecurityConfigurer<O, B>> configurers = getConfigurers(); for (SecurityConfigurer<O, B> configurer : configurers) { configurer.configure((B) this); } }
它的主要工作是迭代调用所有 WebSecurityConfigurer
的 configurer
方法,参数是 WebSeucrity
本身,这又是另外一个重要的个性化入口:
【3、个性化配置入口之 configure(WebSecurity web)
】
我们可以通过继承 WebSecurityConfigurerAdapter
并重写该方法来个性化配置 WebSecurity
。
自此,三个重要的个性化入口都已经被调用,即在实现 WebSecurityConfigurerAdapter
经常需要重写的:
1、configure(AuthenticationManagerBuilder auth); 2、configure(WebSecurity web); 3、configure(HttpSecurity http);
回到webSecurity构建过程,接下去重要的的调用:
O result = performBuild();
该方法在 WebSecurityConfigurerAdapter
中实现,返回的就是过滤器FilterChainProxy,源码如下:
@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<>( 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; }
首先计算出 chainSize
,也就是 ignoredRequests.size() + securityFilterChainBuilders.size();
,如果你不配置 ignoredRequests
,那就是 securityFilterChainBuilders.size()
,也就是 HttpSecurity
的个数,其本质上就是你一共配置几个 WebSecurityConfigurerAdapter
,因为每个 WebSecurityConfigurerAdapter
对应一个 HttpSecurity
,而所谓的 ignoredRequests
就是 FilterChainProxy
的请求,默认是没有的,如果你需要条跳过某些请求不需要认证或授权,可以如下配置:
@Override public void configure(WebSecurity web) throws Exception { web.ignoring().antMatchers("/statics/**"); }
在上面配置中,所有以 /statics
开头请求都将被 FilterChainProxy
忽略。
计算完 chainSize
后,就会创建 List<SecurityFilterChain> securityFilterChains = new ArrayList<>(chainSize);
,遍历所有的 HttpSecurity
,调用 HtppSecurity
的 build()
构建其对应的过滤器链 SecurityFilterChain
实例,并将 SecurityFilterChain
添加到 securityFilterChains
列表中:
for (SecurityBuilder<? extends SecurityFilterChain> securityFilterChainBuilder : securityFilterChainBuilders) { securityFilterChains.add(securityFilterChainBuilder.build()); }
调用 HtppSecurity
的 build()
构建其实和调用 WebSecurity
的 build()
构建类类似,父类中方法一次被执行,最后执行本身的 performBuild()
方法,其源码如下:
@Override protected DefaultSecurityFilterChain performBuild() throws Exception { Collections.sort(filters, comparator); return new DefaultSecurityFilterChain(requestMatcher, filters); }
构建 SecurityFilterChain
主要是完成 RequestMatcher
和对应的过滤器列表,我们都知道在Spring Security中,过滤器执行按顺序顺序的,这个排序就是在 performBuild()
中完成的,也就是:
Collections.sort(filters, comparator);
它通过一个比较器实现了过滤器的排序,这个比较器就是 FilterComparator
,有兴趣的朋友可以自己去了解详情。
最后返回的是 SecurityFilterChain
的默认实现 DefaultSecurityFilterChain
。
构建完所有 SecurityFilterChain
后,创建最为重要的 FilterChainProxy
实例,
FilterChainProxy filterChainProxy = new FilterChainProxy(securityFilterChains);
构造器中传入 SecurityFilterChain
列表,如果开启了Debug模式,还会被包装成 DebugFilter
类型,共开发调试使用,默认是关闭的,可以通过过下面方式开启Debug模式:
@Override public void configure(WebSecurity web) throws Exception { web.debug(true); }
至此Spring Security 初始化完成,我们通过继承 WebSecurityConfigurerAdapter
来代达到个性化配置目的,文中提到了三个重要的个性化入口,并且 WebSecurityConfigurerAdapter
是可以配置多个的,其对应的接口就是会存在多个 SecurityFilterChain
实例,但是它们人仍然在同一个FilterChainProxy中,通过 RequestMatcher
来匹配并传入到对应的 SecurityFilterChain
中执行请求。