内容简介:本文是对 Spring Security Core 4.0.4 Release 进行源码分析的系列文章之一;本系列开始,将讲解有关 Spring Security 的配置相关的内容;本文为作者的原创作品,转载需注明出处;
本文是对 Spring Security Core 4.0.4 Release 进行源码分析的系列文章之一;
本系列开始,将讲解有关 Spring Security 的配置相关的内容;
本文为作者的原创作品,转载需注明出处;
简介
本博文将继续使用 Spring Security 源码分析八:Spring Security 过滤链二 - Demo 例子 中所使用到的例子,来讲解,基于 Spring Boot 的 Java Config 的方式;
@EnableWebSecurity 是用户自定义 Spring Security 过滤链 的入口,是核心,任何相关的认证操作,都将从这里开始;所以,笔者首先从这里入手,开启讲解 Spring Security 配置的相关内容;
配置
重温一下之前在DemoApplication 中所介绍的一个例子,可以看到该例子继承自 WebSecurityConfigurerAdapter 并且添加了 @EnableWebSecurity 注解;
@Configuration @EnableWebSecurity @Order(1) static class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Autowired public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { auth .inMemoryAuthentication() .withUser("user").password("password").roles("USER").and() .withUser("manager").password("password").roles("MANAGER"); } @Override protected void configure(HttpSecurity http) throws Exception { http.antMatcher("/web/**") // the filter chain defined for web request .authorizeRequests() .antMatchers("/web/report/**").hasRole("MANAGER") .anyRequest().authenticated() .and() .formLogin() // login 的相对路径必须与 security chain 的的相对路径吻合,这里是 /web/**;注意 login 分两步,一步是 Getter 会到 login.html,另外一步是从 login.html -> post -> /web/login/ .loginPage("/web/login") // 允许访问 .permitAll(); } }
@EnableWebSecurity
首先来看一下该 annotation 的注解,
Add this annotation to an @Configuration
class to have the Spring Security configuration defined in any WebSecurityConfigurer
or more likely by extending the WebSecurityConfigurerAdapter
base class and overriding individual methods:
可以在任何通过 @Configuration
注解的 WebSecurityConfigurer
和 WebSecurityConfigurerAdapter
类中进行 Spring Security 相关的配置;这个也正是我们上面的例子中所做的那样,但是,问题是,@EnableWebSecurity 的核心功能是什么?它的底层运作机制是什么?先来看看它的源码,
@Retention(value = java.lang.annotation.RetentionPolicy.RUNTIME) @Target(value = { java.lang.annotation.ElementType.TYPE }) @Documented @Import({ WebSecurityConfiguration.class, ObjectPostProcessorConfiguration.class, SpringWebMvcImportSelector.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; }
可以看到,该注解通过 @Import
导入了其它三个配置类, WebSecurityConfiguration.class
、ObjectPostProcessorConfiguration.class 以及 SpringWebMvcImportSelector.class;这里最重要的是 WebSecurityConfiguration.class
,那么它的内部运行机制是什么呢?
WebSecurityConfiguration.class
先看看该类的说明,
Uses a WebSecurity
to create the FilterChainProxy
that performs the web based security for Spring Security.
It then exports the necessary beans. Customizations can be made to WebSecurity by extending WebSecurityConfigurerAdapter and exposing it as a Configuration or implementing WebSecurityConfigurer and exposing it as a Configuration. This configuration is imported when using EnableWebSecurity.
先总结一下它的功能,一句话,根据用户所配置的 Spring Security 配置通过创建出对应FilterChainProxy 并生成相应的 Spring Security 过滤链;那么看看它是如何一步一步做到的呢,在讲解之前,要知道 WebSecurityConfiguration 是通过 @Configuration 进行注解的;
@Configuration public class WebSecurityConfiguration implements ImportAware, BeanClassLoaderAware { ... }
加载用户自定义配置类
webSecurityConfigurers
如何加载用户的自定义配置呢?比如上面所介绍的以及DemoApplication 中所介绍的RestSecurityConfig 的呢?答案就在 WebSecurityConfiguration 的成员变量 webSecurityConfigurers
上;
-
首先,该方法是通过 @Autowired 注解的,也就是说在 Spring 容器初始化的时候,该方法即可被加载;在该方法的加载过程当中,通过 @Value 注解从 Spring 容器中取得 autowiredWebSecurityConfigurersIgnoreParents Spring bean 实例,并从该实例中通过方法getWebSecurityConfigurers()获取得到
webSecurityConfigurers
参数,该参数保存的既是WebSecurityConfig和RestSecurityConfig两个配置类,既是用户通过注解自定义实现的两个有关 Spring Security 配置类,非常之关键
;Wow,这时一个神奇的参数,是的,它的确是;但问题是,该参数是如何获取得到如此重要的配置类信息的呢?关键就在于 autowiredWebSecurityConfigurersIgnoreParents 所对应的 Spring Bean,来看下面这个方法;@Bean public AutowiredWebSecurityConfigurersIgnoreParents autowiredWebSecurityConfigurersIgnoreParents( ConfigurableListableBeanFactory beanFactory) { return new AutowiredWebSecurityConfigurersIgnoreParents(beanFactory); }
该方法通过注解 @Bean 初始化得到一个名为 autowiredWebSecurityConfigurersIgnoreParents 由 Spring 容器所管理的 bean;该 bean 是通过初始化 AutowiredWebSecurityConfigurersIgnoreParents 所的到的,
final class AutowiredWebSecurityConfigurersIgnoreParents { private final ConfigurableListableBeanFactory beanFactory; public AutowiredWebSecurityConfigurersIgnoreParents( ConfigurableListableBeanFactory beanFactory) { Assert.notNull(beanFactory, "beanFactory cannot be null"); this.beanFactory = beanFactory; } @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; } }
该类非常的简单,核心既是上面的方法getWebSecurityConfigurers(),直接从当前的 Spring 容器中去加载类型为 WebSecurityConfigurer 类的实例;回头看看DemoApplication 中所实现的WebSecurityConfig和RestSecurityConfig正好继承自 WebSecurityConfigurer ,所以这两个由用户自定义的 Spring Security 配置类都将在这里被加载;
-
然后依次遍历 webSecurityConfigurer 配置并将其通过 WebSecurity 的 apply() 方法加载入
WebSecurity
对象实例中;为后续的构建进行准备;
创建 FilterChainProxy 实例 ( named “springSecurityFilterChain” )
由前面的系列文章分析可知,FilterChainProxy 在 Spring 容器中的 bean 的名字为 springSecurityFilterChain ,该实例包含 1 个或者多个SecurityFilterChain,并且该实例被 DelegatingFilterProxy 实例所代理,接收并处理由其所转发的请求;那么本章节将探讨的既是 FilterChainProxy 实例是如何被创建的?
答案就在中;
WebSecurity.build
首选,看看 WebSecurity 相关的注解,
The WebSecurity
is created by WebSecurityConfiguration
to create the FilterChainProxy
known as the Spring Security Filter Chain ( springSecurityFilterChain ). The springSecurityFilterChain is the Filter that the DelegatingFilterProxy delegates to.
Customizations to the WebSecurity can be made by creating a WebSecurityConfigurer
or more likely by overriding WebSecurityConfigurerAdapter
.
从其注解可知,WebSecurity 的核心功能既是去创建 FilterChainProxy
;下面看一下 WebSecurity 构建的入口,
可见通过方法上的注解 @Bean 来构建一个名为 springSecurityFilterChain 的 Spring Bean,该 bean 就是 FilterChainProxy
;
由上一小节的第二点分析可知,当通过webSecurityConfigurers获取得到用户自定义配置类以后,将依次的通过调用 WebSecurity
的 apply() 将其加载;其目的其实就是为了后续的构建动作,下面,笔者就来分析一下 WebSecurity 的构建行为;
上面这段代码显示了其核心的构建步骤,其构建的步骤由这样三个流程、以及所构成;将 debug 断点打在 WebSecurityConfig.configure 的方法中,来分析下面这三种情况;
init process
在开始执行该流程以前,build state 将会被置为 INITIALIZING 的状态;
从上述的调用流程中可以清晰的看到, WebSecurityConfig
既用户自定义的 Spring Security 配置类的configure(HttpSecurity)方法将会被调用,同样,用户自定义的 RestSecurityConfig
的configure(HttpSecurity)方法同样会被调用;可以看到这两个类都被 Cglib 生成了相应的代理类,然后,调用 WebSecurityConfigurerAdapter
. getHttp() 方法,该方法中最终调用到 WebSecurityConfig
.configure(HttpSecurity) 方法加载用户自定义的 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); 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; }
可见,该方法中会首先初始化一个 HttpSecurity 对象实例,然后通过模板方法 configure(HttpSecurity) 去加载用户自定义的 Java Config Security 相关的配置;所以,这里比较迷惑人的是,照理说,init 应该只取调用 init 相关的内容,但这里确实却 调用了用户的 configure 相关的内容 ;
因此,加载用户通过 WebSecurityConfig 和 RestSecurityConfig 自定义的有关 HttpSecurity 的配置,这里的配置包括为 HttpSecurity 配置相关的 SecurityConfigurers,以及 intercept-url 等;configure process
执行之前,build state 将会被置为 configuring;该方法调用的是 abstract 方法 WebSecurityConfigurerAdapter
. configure(WebSecurity web) ,也就是说,如果 WebSecurityConfig
或 RestSecurityConfig
实现了 configure(WebSecurity web) 模板方法,将会在这里被调用;当然,显而易见,这里的目的是给用户一个机会去 重载 WebSecurity
的机会;
perform build process
因为 Cglib 代理类的原因,因此 debug 断点需要打在 WebSecurity.performBuild() 方法中,否则断点不会进行;
该过程最核心的两个地方就是由箭头所指向的地方,下面笔者将来分别就这两个关键地方进行分析,
第一个地方,
securityFilterChainBuilder 实现了 SecurityBuilder 接口,该接口提供了一个 build() 接口方法,便于执行 Security 构建相关操作;
public interface SecurityBuilder<O> { /** - Builds the object and returns it or null. * - @return the Object to be built or null if the implementation allows it. - @throws Exception if an error occurred when building the Object */ O build() throws Exception; }
从 debug 的过程中可以看到,该 securityFilterChainBuilder 分别对应的是 WebSecurityCofnig
和 RestSecurityConfig
中被重载 HttpSecurity 对象,可见,这里通过调用 securityFilterChainBuilder.build() 对其执行构建动作,该构建将会生成一个关键的SecurityFilterChain 对象,而 FilterChainProxy 对象正式由多个 SecurityFilterChain 对象实例所构成,具体详情参考中的子流程;
第二个地方,
然后另外一个重要的就是,通过一个 SecurityFilterChains 队列来构造 FilterChainProxy
对象,并将其作为 Filter 接口对象返回;从前面的系列文章总我们可以知道,FilterChainProxy 实际上是 DelegatingFilterProxy 的一个代理……;
HttpSecurity.build
从第 3 点中可以知道,通过 HttpSecurity.build() 方法将会返回一个 SecurityFilterChain 对象,那么这个过程是怎样的呢?首先,HttpSecurity 同样实现了 SecurityBuilder 接口,并且其调用流程同样是通过 AbstractSecurityBuilder.build() 方法开始的,
public abstract class AbstractSecurityBuilder<O> implements SecurityBuilder<O> { ...... public final O build() throws Exception { if (building.compareAndSet(false, true)) { object = doBuild(); return object; } throw new AlreadyBuiltException("This object has already been built"); } ...... }
通过上述代码第 7 行,执行 doBuild() 逻辑,该方法将会调用,AbstractConfiguredSecurityBuilder.doBuild() 方法
public abstract class AbstractConfiguredSecurityBuilder<O, B extends SecurityBuilder<O>> extends AbstractSecurityBuilder<O> { ...... @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 以及 perform build 三个流程所构成的;后续打算新开一个系列来专门讲解 HttpSecurity 对象逻辑,所以这里不打算对 HttpSecurity 的构建流程做深入的分析;直接跳转到最后一步,HttpSecurity.performBuild()
public final class HttpSecurity extends AbstractConfiguredSecurityBuilder<DefaultSecurityFilterChain, HttpSecurity> implements SecurityBuilder<DefaultSecurityFilterChain>, HttpSecurityBuilder<HttpSecurity> { ....... @Override protected DefaultSecurityFilterChain performBuild() throws Exception { Collections.sort(filters, comparitor); return new DefaultSecurityFilterChain(requestMatcher, filters); } ....... }
可见这里将会初始化一个 DefaultSecurityFilterChain 并返回;
设计
从上述的分析后,笔者构建了相关的设计图来总结其相关的流程和逻辑;
类图
整个上述流程中所涉及的类如上图所示,让整个 Spring Security 流转起来的最核心的类是WebSecurityConfiguration;
Sequence
相关业务流程图如下,
看这张图的时候,还需要注意,WebSecurity 与 HttpSecurity 是一对多的关系;另外需要关注的是,从步骤 1.1.1.1.4.1.1.1.1 getHttp() 开始,初始化 HttpSecurity 实例,并为每一个 HttpSecurity 实例进行配置( 通过 SecurityConfigurer 进行配置 ),要注意的是 HttpSecurity 与用户自定义的 WebSecurityConfig 和 RestSecurityConfig 是一对一的关系;当 HttpSecurity 初始化和初始配置结束以后,会将该实例通过 Step 1.1.1.1.4.1.1.1.2 webSecurity.addSecurityFilterChainBuilder(http) 步骤将其注入到 WebSecurity 实例中;
WebSecurity
与 WebSecurity 相互交织,有关联的概念主要有三个个方面;1、,既 WebSecurity 本身是一个 Security Builder 这样一个角色;2、WebSecurityConfigurer,这里主要是用来扩展用户自定义安全链的规则;3、WebSecurity 通过apply() 方法加载用户自定义 WebSecurityConfigurers,也就是说 WebSecurity 包含一个或者多个 WebSecurityConfigurers 对象;下面笔者分别就这三个方面来进行描述;
SecurityBuilder
这部分内容是后续补记的内容,由类图可知,WebSecurity 本身是一个 SecurityBuilder,因此它支持三步构建的方式,这部分参考;
WebSecurityConfigurer
这部分描述了用户自定义的 WebConfigSecurity 和 RestConfigSecurity 的类的形态;他们分别继承自WebSecurityConfigurerAdapter抽象类;
更多有关 SecurityConfigurer 的内容参考 Spring Security 源码分析九:Java config - HttpSecurity & SecurityConfigurer 小节SecurityConfigurer 部分;
WebSecurityConfigurerAdapter
WebSecurityConfigurerAdapter是非常核心的类,用来扩展用户自定义的 Security Chain 的规则,那么下面,笔者就相关的核心方法进行依次介绍
-
configure(HttpSecurity 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(); }
该方法是标准的模板方法,目的是让用户来重载并提供自定义的安全链的相关设置;如果用户没有重载该方法,则使用上述的默认实现;
-
configure(WebSecurity web)
/** * Override this method to configure {@link WebSecurity}. For example, if you wish to * ignore certain requests. */ public void configure(WebSecurity web) throws Exception { }
默认提供的是一个空的模板方法,目的是让用户来重载该方法并定制 WebSecurity 的相关属性;该方法是在的 1.1.1.1.3.1 步骤中被调用;
-
HttpSecurity getHttp()
/** - 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; }
在前面的小节的第 #1 步中,既是在 init build process 中,上述的 getHttp() 将会被调用,并且通过模板方法 configure(http) 去加载用户对 HttpSecurity 的相关的配置;
WebSecurityConfiguration
WebSecurityConfiguration 在WebSecurityConfiguration.class章节中有过细致的介绍;这里的讲解重心是 WebSecurity;
构造 WebSecurity 并加载 WebSecurityConfigurers
WebSecurityConfiguration.class
@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)); 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; }
- 代码第 7 行构造了 WebSecurity 对象;
- 代码第 23 到 25 行,将用户自定义的 web configurers,WebSecurityConfig 和 RestSecurityConfig,载入 WebSecurity 对象实例中;
执行 WebSecurity 构建操作
WebSecurityConfiguration.class
@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(); }
代码第 11 行,开始执行 WebSecurity 实例的构建操作,该构建过程在小节中有过非常详细的介绍;
Sequence
总结(关联关系总结)
搞清楚以下的逻辑,也就搞清楚了 WebSecurity 的作用和地位了;
-
WebSecurity 与 FilterChainProxy 和 DelegatingFilterProxy 是一对一的关系;
FilterChainProxy 对象是由 WebSecurity 对象所构建出来的;
-
WebSecurity 与 HttpSecurity 是一对多的关系;
FilterChainProxy 对象包含多个安全链既 SecurityFilterChain 对象,HttpSecurity 将会负责构建出 SecurityFilterChain 对象;所以,WebSecuirty 包含多个 HttpSecurity;
-
HttpSecurity 与 SecurityFilterChain 是一对一的关系;
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- 以太坊源码分析(36)ethdb源码分析
- [源码分析] kubelet源码分析(一)之 NewKubeletCommand
- libmodbus源码分析(3)从机(服务端)功能源码分析
- [源码分析] nfs-client-provisioner源码分析
- [源码分析] kubelet源码分析(三)之 Pod的创建
- Spring事务源码分析专题(一)JdbcTemplate使用及源码分析
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。