前言
本文是对 Spring Security Core 4.0.4 Release 进行源码分析的系列文章之一;
Spring Security 在 Spring 容器中的初始化过程分为两个部分,首先,第一个部分,解析配置,生成对应的 Bean Definitions,并将对应的 Bean Definitions 保存到 Spring 容器中;其次,根据生成的 Bean Definitions 生成对应的 Bean,并同样将其保存到 Spring 容器中;
备注,有关 Bean Definitions 的解析并注册相关源码分析部分参考 Spring Core Container 源码分析七:注册 Bean Definitions,有关对 Bean Definitions 中所注册的 Bean 进行初始化的源码分析部分参考 Spring Core Container 源码分析三:Spring Beans 初始化流程分析
本文是对 Spring Security 初始化过程的第一部分进行的描述;
本文将使用 Spring Security 源码分析四:Spring MVC 例子 作为调试用例,进行源码分析;
本文为作者的原创作品,转载需注明出处;
WebApplicationContext 初始化过程
这里不是分析的重点,但是是在 Web 容器中初始化 Spring 容器的始发点;主要是通过 Web 容器加载 listener ContextLoaderListener,由该 listener 对 Spring 容器进行初始化的;在 web.xml 中的相关配置如下,
1 | <listener> |
整个初始化流程如下所述,
通过调用 ContextLoaderListener 的初始化方法 initWebApplicationContext(ServeltContext) 对 Spring Application Context 既容器进行初始化;可以看到,最终将会触发 XmlWebApplicationContext#refresh() 方法开始对 Spring 容器进行初始化,该初始化流程参考 Spring 容器初始化流程;总结起来主要有两个步骤,
解析并注册 Bean Definitions
该过程主要就是通过解析 Spring Security 相关的 elements 并生成对应的 Bean Definitions,然后将其注册到容器中;其生成的过程是,解析 XML 配置文件或者是 Java Config 配置类,然后生成相关的 Bean Definitions,里面包含了配置文件中的所描述的参数、约束条件等等;补充,有关 Bean Definitions 更具体的介绍参考 Spring Core Container 源码分析七:注册 Bean Definitions;
那么 Spring 容器初始化的时候,是如何知道哪些 Bean Definitions 是需要被加载的呢?
以 Spring Security 源码分析四:Spring MVC 例子为例,Spring 容器是通过解析 applicationContext-security.xml 配置文件中的 elements 而知道的哪些 Spring Security 相关的 Bean Definitions 将会被创建、初始化并加入 Spring 容器中;其中重要的包含两块,
Spring Security Namespace
1
2xmlns="http://www.springframework.org/schema/security"
xsi:schemaLocation="http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security.xsd"Spring Security 为上述的 Namespace 的定义默认配置了相关的 Handler 既 SecurityNamespaceHandler 来解析并创建相关的 Bean Definitions;那么 SecurityNamespaceHandler 是如何被初始化的呢?解析 bean definitions 中的 parse custom element process 步骤,
相关代码,
1
2
3
4
5
6
7
8
9public BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd) {
String namespaceUri = getNamespaceURI(ele);
NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
if (handler == null) {
error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);
return null;
}
return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
}该步骤中,得到的参数 namespaceUri 正好是 Spring Security 的 namespace uri 既 http://www.springframework.org/schema/security 由其所解析得到的正是 SecurityNamespaceHandler 实例对象;
Spring Security Conf
1
2
3
4
5
6
7
8
9
10
11
12<http auto-config="true">
<intercept-url pattern="/**" access="permitAll"/>
</http>
<authentication-manager>
<authentication-provider>
<user-service>
<user name="user" password="password" authorities="ROLE_USER" />
<user name="admin" password="password" authorities="ROLE_USER,ROLE_ADMIN"/>
</user-service>
</authentication-provider>
</authentication-manager>这里定义了 http、authentication-manager、authentiation-provider 等相关的 element 元素;在解析这些元素并生成相应的 Bean Definitions 的时候,将会采用 #1 所介绍的 SecurityNamespaceHandler 来进行处理,那么 SecurityNamespaceHandler 是如何进行处理的呢?
- 在 SecurityNamespaceHandler 的初始化过程中会调用其 init 方法,在过程中会根据不同的 element
初始化大量的 Parsers
; 这部分的详细内容参考初始化 Parsers - 然后根据不同的 element 采用不同的 Parser 进行解析,将解析得到的 Bean Definition 注入到 Spring 容器中;这部分参考解析 Beans
- 在 SecurityNamespaceHandler 的初始化过程中会调用其 init 方法,在过程中会根据不同的 element
初始化 Parsers
由上述分析 #2.1 小节中可知,在 SecurityNamespaceHandler 的初始化过程当中将会根据不同的 element 初始化不同的 Parser;下面,笔者就此剖析一下 SecurityNamespaceHandler 的内部初始化过程,看看 Parsers 是如何生成的;初始化 SecurityNamespaceHandler 的时候,会调用其 init 方法,去初始化各种各样的 parsers,相关代码如下,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
38public void init() {
loadParsers();
}
"deprecation") (
private void loadParsers() {
// Parsers
parsers.put(Elements.LDAP_PROVIDER, new LdapProviderBeanDefinitionParser());
parsers.put(Elements.LDAP_SERVER, new LdapServerBeanDefinitionParser());
parsers.put(Elements.LDAP_USER_SERVICE, new LdapUserServiceBeanDefinitionParser());
parsers.put(Elements.USER_SERVICE, new UserServiceBeanDefinitionParser());
parsers.put(Elements.JDBC_USER_SERVICE, new JdbcUserServiceBeanDefinitionParser());
parsers.put(Elements.AUTHENTICATION_PROVIDER,
new AuthenticationProviderBeanDefinitionParser());
parsers.put(Elements.GLOBAL_METHOD_SECURITY,
new GlobalMethodSecurityBeanDefinitionParser());
parsers.put(Elements.AUTHENTICATION_MANAGER,
new AuthenticationManagerBeanDefinitionParser());
parsers.put(Elements.METHOD_SECURITY_METADATA_SOURCE,
new MethodSecurityMetadataSourceBeanDefinitionParser());
// Only load the web-namespace parsers if the web classes are available
if (ClassUtils.isPresent(FILTER_CHAIN_PROXY_CLASSNAME, getClass()
.getClassLoader())) {
parsers.put(Elements.DEBUG, new DebugBeanDefinitionParser());
parsers.put(Elements.HTTP, new HttpSecurityBeanDefinitionParser());
parsers.put(Elements.HTTP_FIREWALL, new HttpFirewallBeanDefinitionParser());
parsers.put(Elements.FILTER_SECURITY_METADATA_SOURCE,
new FilterInvocationSecurityMetadataSourceParser());
parsers.put(Elements.FILTER_CHAIN, new FilterChainBeanDefinitionParser());
filterChainMapBDD = new FilterChainMapBeanDefinitionDecorator();
}
if (ClassUtils.isPresent(MESSAGE_CLASSNAME, getClass().getClassLoader())) {
parsers.put(Elements.WEBSOCKET_MESSAGE_BROKER,
new WebSocketMessageBrokerSecurityBeanDefinitionParser());
}
}
上述代码所要完成的逻辑就是,为解析不同的 element 注册不同的 parser,关系如下所述,还有很多,只罗列了我所感兴趣的
Element | Parsers |
---|---|
http | HttpSecurityBeanDefinitionParser |
logout | 没有在这里定义 Parser |
anonymous | 没有在这里定义 Parser |
user-service | UserServiceBeanDefinitionParser |
remember-me | 没有在这里定义 Parser |
filter-chain | FilterChainBeanDefinitionParser |
jdbc-user-service | JdbcUserServiceBeanDefinitionParser |
authentication-manager | AuthenticationManagerBeanDefinitionParser |
authentication-provider | AuthenticationProviderBeanDefinitionParser |
global-method-security | GlobalMethodSecurityBeanDefinitionParser |
more and more ... | ... |
解析 Elements
好的,Parsers 在初始化 SecurityNamespaceHandler 的时候创建好了,本小节将会介绍 Elements 是如何通过 Parsers 进行解析并得到相关的 Bean Definitions 的;回到 parse custom element process 步骤,执行余下的 parse 部分代码逻辑,既是解析 Elements 的逻辑的地方,1
2
3
4
5
6
7
8
9public BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd) {
String namespaceUri = getNamespaceURI(ele);
NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
if (handler == null) {
error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);
return null;
}
return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
}
下面我们看看上述代码第 8 行,parse 的逻辑,这里,作者以 demo 中的 Spring Security 有关的 配置 来进行分析,该配置中包含两个相关的元素 <http> 和 <authentication-manager>,下面就来分析一下,这两个元素在 Spring Security 的加载过程中是如何被解析的;
http
根据当前 Element 的类型 http 得到之前注册的 HttpSecurityBeanDefinitionParser,然后执行其 parse 操作,下面分析其源码,
1 | "unchecked" }) ({ |
主要执行了如下几个步骤,
- 初始化得到 http 元素的 CompositeComponentDefinition,既是 bean definition
注册 FilterChainProxy 的 bean definition 以及与之相关的 bean definitions,有 FilterChain 和 FilterChainValidator 相关的 bean definitions,其执行的源码如下,
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25static void registerFilterChainProxyIfNecessary(ParserContext pc, Object source) {
if (pc.getRegistry().containsBeanDefinition(BeanIds.FILTER_CHAIN_PROXY)) {
return;
}
// Not already registered, so register the list of filter chains and the
// FilterChainProxy
BeanDefinition listFactoryBean = new RootBeanDefinition(ListFactoryBean.class);
listFactoryBean.getPropertyValues().add("sourceList", new ManagedList());
// 1. 注册 filter-chains bean definitions
pc.registerBeanComponent(new BeanComponentDefinition(listFactoryBean,
BeanIds.FILTER_CHAINS));
// 2. 生成并注册 filter-chain-proxy bean definitions
BeanDefinitionBuilder fcpBldr = BeanDefinitionBuilder
.rootBeanDefinition(FilterChainProxy.class);
fcpBldr.getRawBeanDefinition().setSource(source);
fcpBldr.addConstructorArgReference(BeanIds.FILTER_CHAINS);
fcpBldr.addPropertyValue("filterChainValidator", new RootBeanDefinition(
DefaultFilterChainValidator.class));
BeanDefinition fcpBean = fcpBldr.getBeanDefinition();
pc.registerBeanComponent(new BeanComponentDefinition(fcpBean,
BeanIds.FILTER_CHAIN_PROXY));
// 3. 设置别名
pc.getRegistry().registerAlias(BeanIds.FILTER_CHAIN_PROXY,
BeanIds.SPRING_SECURITY_FILTER_CHAIN);
}为当前的 filterChains 新增与当前 http element 相关的 filter chain,关键代码在第 17 行
1
filterChains.add(createFilterChain(element, pc));
下面来看看 createFilterChain(element, pc) 做了什么,
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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59private BeanReference createFilterChain(Element element, ParserContext pc) {
// 1. 得到 http 元素的属性值 security,看其是否设置为 none
boolean secured = !OPT_SECURITY_NONE.equals(element.getAttribute(ATT_SECURED));
if (!secured) {
// 一般都不会将其 http 的 security 设置为 null,所以,这里的代码就直接省略掉了,不做分析;
...
}
// 2. 生成 port 相关的 bean definitions, portMapper 和 portResolver;
final BeanReference portMapper = createPortMapper(element, pc);
final BeanReference portResolver = createPortResolver(portMapper, pc);
// 3. 这里要注意了,生成了相关的 Authentication Manager 相关的 Authentication Provider 的 bean definitions;
ManagedList<BeanReference> authenticationProviders = new ManagedList<BeanReference>();
BeanReference authenticationManager = createAuthenticationManager(element, pc,
authenticationProviders);
boolean forceAutoConfig = isDefaultHttpConfig(element);
// 4. HttpConfigurationBuilder 是构建 http 元素相关的 bean definitions 的帮助类
// 这个帮助类比较的重要,里面定了各种各样需要的帮助类;
HttpConfigurationBuilder httpBldr = new HttpConfigurationBuilder(element,
forceAutoConfig, pc, portMapper, portResolver, authenticationManager);
// 这里的目的是通过借助 AuthenticationConfigBuilder 类的实例方法,帮助进一步完善并构建 HttpConfigurationBuilder 实例 httpBldr,
AuthenticationConfigBuilder authBldr = new AuthenticationConfigBuilder(element,
forceAutoConfig, pc, httpBldr.getSessionCreationPolicy(),
httpBldr.getRequestCache(), authenticationManager,
httpBldr.getSessionStrategy(), portMapper, portResolver,
httpBldr.getCsrfLogoutHandler());
// 设置 Logout Handlers
httpBldr.setLogoutHandlers(authBldr.getLogoutHandlers());
// 设置认证的登录页面
httpBldr.setEntryPoint(authBldr.getEntryPointBean());
// 设置拒绝访问的 Handler
httpBldr.setAccessDeniedHandler(authBldr.getAccessDeniedHandlerBean());
authenticationProviders.addAll(authBldr.getProviders());
List<OrderDecorator> unorderedFilterChain = new ArrayList<OrderDecorator>();
// 总结第4步,上述的 httpBldr 和 authBldr 的最终目的都是为了创建所有的 fitlers
// 5. 收集所有相关的 filters,并排序,最终得到排序后的 filterChain bean definitions
unorderedFilterChain.addAll(httpBldr.getFilters());
unorderedFilterChain.addAll(authBldr.getFilters());
unorderedFilterChain.addAll(buildCustomFilterList(element, pc));
Collections.sort(unorderedFilterChain, new OrderComparator());
checkFilterChainOrder(unorderedFilterChain, pc, pc.extractSource(element));
// The list of filter beans
List<BeanMetadataElement> filterChain = new ManagedList<BeanMetadataElement>();
for (OrderDecorator od : unorderedFilterChain) {
filterChain.add(od.bean);
}
// 6. 创建 filter chain 相关的 bean definitions
return createSecurityFilterChainBean(element, pc, filterChain);
}归纳起来主要有如下几个步骤
- 当前的 http 是否是 insecured? 如果不需要安全措施,则无需后续的 Authentication Manager 和相关的 Filter 等;
- 生成 port 相关的 bean definitions, portMapper 和 portResolver;
顾名思义,这些 bean definitions 是用来进行 port 相关映射的; - 生成了相关的 Authentication Manager 相关的 Authentication Provider 的 bean definitions;这部分内容参考生成 Authentication Provider 的 bean definitions
- 通过借助 HttpConfigurationBuilder 和 AuthenticationConfigBuilder 生成大量与 http 元素相关的 filters,这里面涉及到了 CsrFilter、SecurityContextPersistenceFilter、SessionManagementFilters、RequestCacheFilter、ServletApiFilter、AddHeadersFilter,以及向 SessionFilter 中添加 logout handlers,向 ServletApiFilter 中添加 authenticationEntryPoint,以及向 csrfFilter 中添加 accessDeniedHandler 等等非常重要的逻辑,该部分的详细介绍参考初始化 filters 及其 handlers 相关的 bean definitions
- 收集上述第 4 步生成的所有相关的 filters 并进行排序;
最后初始化得到 DefaultSecurityFilterChain bean definition 并返回,下面分析其源码,
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
43
44
45private BeanReference createSecurityFilterChainBean(Element element,
ParserContext pc, List<?> filterChain) {
BeanMetadataElement filterChainMatcher;
// 1. 构造 filter chain matcher
String requestMatcherRef = element.getAttribute(ATT_REQUEST_MATCHER_REF);
String filterChainPattern = element.getAttribute(ATT_PATH_PATTERN);
if (StringUtils.hasText(requestMatcherRef)) {
if (StringUtils.hasText(filterChainPattern)) {
pc.getReaderContext().error(
"You can't define a pattern and a request-matcher-ref for the "
* "same filter chain", pc.extractSource(element));
}
filterChainMatcher = new RuntimeBeanReference(requestMatcherRef);
}
else if (StringUtils.hasText(filterChainPattern)) {
filterChainMatcher = MatcherType.fromElement(element).createMatcher(
filterChainPattern, null);
}
else {
filterChainMatcher = new RootBeanDefinition(AnyRequestMatcher.class);
}
// 2. 创建 DefaultSecurityFilterChain bean definition,并将 filterChain 作为其构造函数;
BeanDefinitionBuilder filterChainBldr = BeanDefinitionBuilder
.rootBeanDefinition(DefaultSecurityFilterChain.class);
filterChainBldr.addConstructorArgValue(filterChainMatcher);
filterChainBldr.addConstructorArgValue(filterChain);
BeanDefinition filterChainBean = filterChainBldr.getBeanDefinition();
String id = element.getAttribute("name");
if (!StringUtils.hasText(id)) {
id = element.getAttribute("id");
if (!StringUtils.hasText(id)) {
id = pc.getReaderContext().generateBeanName(filterChainBean);
}
}
// 3. 注册,并作为 RuntimeBeanReference 返回
pc.registerBeanComponent(new BeanComponentDefinition(filterChainBean, id));
return new RuntimeBeanReference(id);
}主要完了三个步骤,
- 构造 filter chain matcher
- 创建 DefaultSecurityFilterChain bean definition,并将 filterChain 和 filterChainMatcher 作为其构造函数;
- 注册,并作为 RuntimeBeanReference 返回
- 最后一步,正如其方法名所示,pop 弹出当前 ParserContext 中所有的 bean definitions 并注册到 Spring 容器中;
生成 Authentication Provider 的 bean definitions
该步骤发生在解析 http 元素的步骤 3.3,是其相关的子步骤;相关源码如下所述,
HttpSecurityBeanDefinitionParser.java
1 | /** |
Ok, 上面的代码到底想完成一个什么样的逻辑呢?实际上主要是判断,用户是否通过 http 元素的属性 authentication-manager-ref 自定义过相关的 Authentication Provider,
- 如果定义过,那么这里通过参数 authManager 所定义的 ProviderManager 的构造函数将会填充为该自定义的 Authentication Provider;注意,这里不要被属性的名字 authentication-manager-ref 名字所迷惑,它不是让你自己再去定义一个 Authentication Manager,而是让你去自定义一个 Authentication Provider;
- 如果没有定义过,那么将会通过系统内置的 AuthenticationManagerFactoryBean;
初始化 filters 及其 handlers 相关的 bean definitions
该步骤发生在解析 http 元素的步骤 3.4,是对其有关部分的详细描述;首先,相关部分的源码如下,
HttpSecurityBeanDefinitionParser#createFilterChain(Element, ParserContext)1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19// 4. HttpConfigurationBuilder 是构建 http 元素相关的 bean definitions 的帮助类
// 这个帮助类比较的重要,里面定了各种各样需要的帮助类;
HttpConfigurationBuilder httpBldr = new HttpConfigurationBuilder(element,
forceAutoConfig, pc, portMapper, portResolver, authenticationManager);
// 这里的目的是通过借助 AuthenticationConfigBuilder 类的实例方法,帮助进一步完善并构建 HttpConfigurationBuilder 实例 httpBldr,
AuthenticationConfigBuilder authBldr = new AuthenticationConfigBuilder(element,
forceAutoConfig, pc, httpBldr.getSessionCreationPolicy(),
httpBldr.getRequestCache(), authenticationManager,
httpBldr.getSessionStrategy(), portMapper, portResolver,
httpBldr.getCsrfLogoutHandler());
// 设置 Logout Handlers
httpBldr.setLogoutHandlers(authBldr.getLogoutHandlers());
// 设置认证的登录页面
httpBldr.setEntryPoint(authBldr.getEntryPointBean());
// 设置拒绝访问的 Handler
httpBldr.setAccessDeniedHandler(authBldr.getAccessDeniedHandlerBean());
authenticationProviders.addAll(authBldr.getProviders());
List<OrderDecorator> unorderedFilterChain = new ArrayList<OrderDecorator>();
// 总结第4步,上述的 httpBldr 和 authBldr 的最终目的都是为了创建所有的 fitlers
首先,来看看上述代码第 3 ~ 4 行,初始化了 HttpConfigurationBuilder 并得到一个实例 httpBldr,那么该构造函数内部做了什么事情呢?
1 | public HttpConfigurationBuilder(Element element, boolean addAllAuth, |
从整个处理逻辑上来区分,主要包含了四块
为成员变量进行初始化并赋值;
解析并处理 http 的子元素 intercept-url,从后续的代码分析可知,这里其实只是遍历所有的 intercept-url,然后做了一个简单的验证,判断该 intercept-url 上是否包含了 filter 属性,如果是,则报错;
Session Policy 既 Session 策略;判断 lt;httpgt; 元素上是否配置了 create-session 属性,如果配置了,则判断其值来决定每一次访问的 session 该如何创建,看看 createPolicy(String) 方法的源码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17private SessionCreationPolicy createPolicy(String createSession) {
if ("ifRequired".equals(createSession)) {
return SessionCreationPolicy.IF_REQUIRED;
}
else if ("always".equals(createSession)) {
return SessionCreationPolicy.ALWAYS;
}
else if ("never".equals(createSession)) {
return SessionCreationPolicy.NEVER;
}
else if ("stateless".equals(createSession)) {
return SessionCreationPolicy.STATELESS;
}
throw new IllegalStateException("Cannot convert " + createSession + " to "
+ SessionCreationPolicy.class.getName());
}可以看到,根据 create-session 属性的不同属性值,有四种创建 Session 的策略,他们分别是
- IF_REQUIRED
- ALWAYS
- NEVER
- STATELESS
如果没有显示指定该属性 create-session 的值,那么默认将会使用 IF_REQUIRED
创建所有相关的 filters
- Csrf Filter
该过程是通过 CsrfBeanDefinitionParser 解析得到的 - SecurityContextPersistenceFilter
该 Filter 主要是通过创建 HttpSessionSecurityContextRepository 的 bean definitions 来初始化该 Filter 的 bean definitions;备注,HttpSessionSecurityContextRepository 非常关键,它定义了 Http Session 里面的值的存储策略,如果想要将 Http Session 中的值转储到数据库或者分布式缓存中,那么需要对它进行配置;这里不对它展开来描述了; - SessionManagementFilters
createSessionManagementFilters() 有关的这段方法的代码真没办法一口气对完,一个 function 总共差不多 200 行,而且没什么注释… 吐槽,有时候 Spring 的源码读得真让人头大… 总体而言,就是为了对 session 进行方方面面的控制,而初始化相关的 bean definitions,里面不仅包含对 session 的一系列常规控制的 bean definitions 还包含了 CSRF 相关的东西.. 等将来有时间了,再慢慢啃这里的代码吧.. - WebAsyncManagerFilter
- RequestCacheFilter
解析 request-cache 属性 - ServletApiFilter
初始化 SecurityContextHolderAwareRequestFilter 相关的 Bean Definitions - JaasApiFilter
初始化 JaasApiIntegrationFilter 相关的 Bean Definitions - ChannelProcessingFilter
初始化 ChannelProcessingFilter 相关的 Bean Definitions,包含与 http、https 以及 retryWithHttp、retryWithHttps 等相关的属性 FilterSecurityInterceptor
创建与 Security 相关拦截器的 bean definitions
初始化 FilterSecurityInterceptor 相关的 Bean Definitions,过程中,会创建 id 为 accessDecisionManager、authenticationManager 所相关的 bean definitions,最核心的相关代码摘录如下,1
2
3
4
5
6
7...
voters.add(new RootBeanDefinition(RoleVoter.class));
voters.add(new RootBeanDefinition(AuthenticatedVoter.class));
....
accessDecisionMgr = new RootBeanDefinition(AffirmativeBased.class);
accessDecisionMgr.getConstructorArgumentValues().addGenericArgumentValue(voters);
accessDecisionMgr.setSource(pc.extractSource(httpElt));AddHeadersFilter
该 Filter 是通过 HeadersBeanDefinitionParser 进行解析并初始化的,里面通过解析 http 元素及其子元素和相关属性,分别生成与之相关的 CacheControlHeadersWriter、HstsHeaderWriter、XXssProtectionHeaderWriter 等于 HeaderWriter 相关的 Bean definitions
- Csrf Filter
其次,再看上述代码第 6 ~ 10 行,构造了一个 AuthenticationConfigBuilder 实例,那么该构造函数内部又做了什么事情呢?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
29public AuthenticationConfigBuilder(Element element, boolean forceAutoConfig,
ParserContext pc, SessionCreationPolicy sessionPolicy,
BeanReference requestCache, BeanReference authenticationManager,
BeanReference sessionStrategy, BeanReference portMapper,
BeanReference portResolver, BeanMetadataElement csrfLogoutHandler) {
this.httpElt = element;
this.pc = pc;
this.requestCache = requestCache;
autoConfig = forceAutoConfig
| "true".equals(element.getAttribute(ATT_AUTO_CONFIG));
this.allowSessionCreation = sessionPolicy != SessionCreationPolicy.NEVER
&& sessionPolicy != SessionCreationPolicy.STATELESS;
this.portMapper = portMapper;
this.portResolver = portResolver;
this.csrfLogoutHandler = csrfLogoutHandler;
createAnonymousFilter();
createRememberMeFilter(authenticationManager);
createBasicFilter(authenticationManager);
createFormLoginFilter(sessionStrategy, authenticationManager);
createOpenIDLoginFilter(sessionStrategy, authenticationManager);
createX509Filter(authenticationManager);
createJeeFilter(authenticationManager);
createLogoutFilter();
createLoginPageFilterIfNeeded();
createUserDetailsServiceFactory();
createExceptionTranslationFilter();
}
其构造方法和 HttpConfigurationBuilder 是何曾的相似;不过里面又创建了更多的 filters 相关 bean definitions,
AnonymousFilter
如果配置了 http 的子元素 anonymous,并且 anonymous 没有被禁用的话,那么将初始化 AnonymousAuthenticationFilter 的 bean definitionRememberMeFilter
解析 <remember-me>子元素,如果配置了 http 的子元素 remember-me,并且 remember-me 没有被禁用的话,会创建与 remember-me 相关的 RememberMeAuthenticationFilter 以及 RememberMeAuthenticationProvider 的 bean definitions;BasicFilter
解析 <http-basic> 元素,如果<http> 的子元素 <http-basic>被激活,那么将会初始化与 Http Basic 认证相关的 BasicAuthenticationFilter、BasicAuthenticationEntryPoint 相关的 bean definitions,其中 BasicAuthenticationEntryPoint 可以通过 entry-point-ref 属性自行定义;FormLoginFilter
解析 <form-login> 子元素,看看其解析的核心代码,1
2
3
4
5
6
7
8
9
10FormLoginBeanDefinitionParser parser = new FormLoginBeanDefinitionParser(
"/login", "POST", AUTHENTICATION_PROCESSING_FILTER_CLASS,
requestCache, sessionStrategy, allowSessionCreation, portMapper,
portResolver);
parser.parse(formLoginElt, pc);
formFilter = parser.getFilterBean();
formEntryPoint = parser.getEntryPointBean();
loginProcessingUrl = parser.getLoginProcessingUrl();
formLoginPage = parser.getLoginPage();这里解释了,为什么我在 Spring Security 配置文件里面什么也没有配置,但是 /login 却是我的登录路径,这里,源代码是直接 hard code 将 /login 作为访问路径进行初始化的;该步骤主要是初始化与 Form Login 相关的 LoginUrlAuthenticationEntryPoint、AntPathRequestMatcher、SimpleUrlAuthenticationFailureHandler、SavedRequestAwareAuthenticationSuccessHandler 等相关的 bean definitions;
OpenIDLoginFilter
X509Filter
JeeFilter
LogoutFilter
解析 <logout> 子元素,生成与 logout 行为相关的 LogoutFilter、successHandlerRef (可以通过设置属性 success-handler-ref 自定义 logout 成功以后的回调方法)、SecurityContextLogoutHandler (该 Handler 是用来处理登出以后,session 被清空的处理方法,通过属性 invalidate-session = “true” 来设置)、还可以通过设置 logout-url、logout-success-url、delete-cookies 来分别设置调用登出的路径、登出成功以后跳转的路径以及是否需要清空 cookie (这里的常规做法是,不清空,所以不要轻易去设置 delete-cookies );
LogoutFilter 里面的逻辑比较清晰,如果只想要搞懂其中的流转逻辑,LogoutFilter 是一个不二的选择;LoginPageFilterIfNeeded
UserDetailsServiceFactory
这一步就是用来初始化 UserDetailsServiceFactoryBean 的 bean definitions 的;ExceptionTranslationFilter
authentication-manager
这里将会采用 AuthenticationManagerBeanDefinitionParser 对元素 authentication-manager 进行解析,下面作者就带领代价来读一读这部分的核心代码,
AuthenticationManagerBeanDefinitionParser.java1
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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73public BeanDefinition parse(Element element, ParserContext pc) {
...
// 1. 初始化 ProviderManager Bean Definition
BeanDefinitionBuilder providerManagerBldr = BeanDefinitionBuilder
.rootBeanDefinition(ProviderManager.class);
String alias = element.getAttribute(ATT_ALIAS);
// 2. 构造 Authentiation Provider Bean Definitions
List<BeanMetadataElement> providers = new ManagedList<BeanMetadataElement>();
NamespaceHandlerResolver resolver = pc.getReaderContext()
.getNamespaceHandlerResolver();
NodeList children = element.getChildNodes();
// 遍历其一级子元素,得到所有的 authentication-provider 元素,然后挨个解析;
for (int i = 0; i < children.getLength(); i++) {
Node node = children.item(i);
if (node instanceof Element) {
Element providerElt = (Element) node;
if (StringUtils.hasText(providerElt.getAttribute(ATT_REF))) {
if (providerElt.getAttributes().getLength() > 1) {
pc.getReaderContext().error(
"authentication-provider element cannot be used with other attributes "
+ "when using 'ref' attribute",
pc.extractSource(element));
}
NodeList providerChildren = providerElt.getChildNodes();
for (int j = 0; j < providerChildren.getLength(); j++) {
if (providerChildren.item(j) instanceof Element) {
pc.getReaderContext().error(
"authentication-provider element cannot have child elements when used "
+ "with 'ref' attribute",
pc.extractSource(element));
}
}
providers.add(new RuntimeBeanReference(providerElt
.getAttribute(ATT_REF)));
}
// 这里开始解析 authentication-provider
else {
BeanDefinition provider = resolver.resolve(
providerElt.getNamespaceURI()).parse(providerElt, pc);
Assert.notNull(provider, "Parser for " + providerElt.getNodeName()
+ " returned a null bean definition");
String providerId = pc.getReaderContext().generateBeanName(provider);
pc.registerBeanComponent(new BeanComponentDefinition(provider,
providerId));
providers.add(new RuntimeBeanReference(providerId));
}
}
}
// 如果压根没有配置 authentication-provider,这里直接使用 NullAuthenticationProvider
if (providers.isEmpty()) {
providers.add(new RootBeanDefinition(NullAuthenticationProvider.class));
}
providerManagerBldr.addConstructorArgValue(providers);
if ("false".equals(element.getAttribute(ATT_ERASE_CREDENTIALS))) {
providerManagerBldr.addPropertyValue("eraseCredentialsAfterAuthentication",
false);
}
.....
pc.popAndRegisterContainingComponent();
return null;
}
上述代码,删除了部分不重要的代码;注意,当前传入的参数 element 是 authentication-manager,表示对它进行解析;上面这段代码的逻辑比较清晰,拿到 authentication-manager 元素以后,然后遍历其一级子元素,依次解析所有相关的 authentication-provider 子元素,解析的过程中将会使用 AuthenticationProviderBeanDefinitionParser 进行解析,该解析过程中,将会解析如下的属性或元素
- 解析子元素 password-encoder,解析用户自定义的相关的 password-encoder
- 获取子元素 user-service,
若不存在子元素 user-service,则判断是否有有子元素 jdbc-user-service
若不存在子元素 user-service,则判断是否有有子元素 ldap-user-service - 解析属性 user-service-ref,那么解析该属性,创建用户自定义的 UserDetailsService bean definitiion;
- 解析子元素 user-service,根据 user-service 的不同类型对 user-service 进行解析,按照这里使用的 demo 中所使用到的 user-service 的配置,这里解析的过程中将会使用到 InMemoryUserDetailsManager;在解析的过程中,还会继续解析 cache-ref 属性,并创建 CachingUserDetailsService 相关的 bean definition;
- 最后,如果没有明确定义使用哪个 authentication-provider 将会默认使用 DaoAuthenticationProvider 并创建其相关的 bean definition;
总结
此步骤主要是根据用户相关的 Spring Security 的配置,在 Spring 容器中初始化相关的 bean definitions;