Spring Security 源码分析五:Spring Security 在容器中初始化过程一

前言

本文是对 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
2
3
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

整个初始化流程如下所述,

通过调用 ContextLoaderListener 的初始化方法 initWebApplicationContext(ServeltContext) 对 Spring Application Context 既容器进行初始化;可以看到,最终将会触发 XmlWebApplicationContext#refresh() 方法开始对 Spring 容器进行初始化,该初始化流程参考 Spring 容器初始化流程;总结起来主要有两个步骤,

  1. 解析并注册 Bean Definitions;
  2. 初始化 Beans;

解析并注册 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 容器中;其中重要的包含两块,

  1. Spring Security Namespace

    1
    2
    xmlns="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
    9
    public 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 实例对象;

  2. 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

初始化 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
38
public void init() {
loadParsers();
}

@SuppressWarnings("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,关系如下所述,还有很多,只罗列了我所感兴趣的

ElementParsers
httpHttpSecurityBeanDefinitionParser
logout没有在这里定义 Parser
anonymous没有在这里定义 Parser
user-serviceUserServiceBeanDefinitionParser
remember-me没有在这里定义 Parser
filter-chainFilterChainBeanDefinitionParser
jdbc-user-serviceJdbcUserServiceBeanDefinitionParser
authentication-managerAuthenticationManagerBeanDefinitionParser
authentication-providerAuthenticationProviderBeanDefinitionParser
global-method-securityGlobalMethodSecurityBeanDefinitionParser
more and more ......

解析 Elements

好的,Parsers 在初始化 SecurityNamespaceHandler 的时候创建好了,本小节将会介绍 Elements 是如何通过 Parsers 进行解析并得到相关的 Bean Definitions 的;回到 parse custom element process 步骤,执行余下的 parse 部分代码逻辑,既是解析 Elements 的逻辑的地方,

1
2
3
4
5
6
7
8
9
public 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@SuppressWarnings({ "unchecked" })
public BeanDefinition parse(Element element, ParserContext pc) {
// 1. 初始化得到 CompositeComponentDefinition
CompositeComponentDefinition compositeDef = new CompositeComponentDefinition(
element.getTagName(), pc.extractSource(element));
pc.pushContainingComponent(compositeDef);
// 2. 注册 FilterChainProxy
registerFilterChainProxyIfNecessary(pc, pc.extractSource(element));

// 3. 下面就是为当前的 filterChains 加入当前 http element 相关的新的 filter chain
// Obtain the filter chains and add the new chain to it
BeanDefinition listFactoryBean = pc.getRegistry().getBeanDefinition(
BeanIds.FILTER_CHAINS);
List<BeanReference> filterChains = (List<BeanReference>) listFactoryBean
.getPropertyValues().getPropertyValue("sourceList").getValue();
// 由下面的分析可知,createFilterChain(element, pc) 返回的正是一个 DefaultSecurityFilterChain
filterChains.add(createFilterChain(element, pc));

pc.popAndRegisterContainingComponent();
return null;
}

主要执行了如下几个步骤,

  1. 初始化得到 http 元素的 CompositeComponentDefinition,既是 bean definition
  2. 注册 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
    25
    static 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);
    }
  3. 为当前的 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
    59
    private 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
      45
      private 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 返回
  4. 最后一步,正如其方法名所示,pop 弹出当前 ParserContext 中所有的 bean definitions 并注册到 Spring 容器中;

生成 Authentication Provider 的 bean definitions

该步骤发生在解析 http 元素的步骤 3.3,是其相关的子步骤;相关源码如下所述,
HttpSecurityBeanDefinitionParser.java

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
/**
* Creates the internal AuthenticationManager bean which uses either the externally
* registered (global) one as a parent or the bean specified by
* "authentication-manager-ref".
*
* All the providers registered by this <http> block will be registered with the
* internal authentication manager.
*/
private BeanReference createAuthenticationManager(Element element, ParserContext pc,
ManagedList<BeanReference> authenticationProviders) {
String parentMgrRef = element.getAttribute(ATT_AUTHENTICATION_MANAGER_REF);
BeanDefinitionBuilder authManager = BeanDefinitionBuilder
.rootBeanDefinition(ProviderManager.class);
authManager.addConstructorArgValue(authenticationProviders);

if (StringUtils.hasText(parentMgrRef)) {
RuntimeBeanReference parentAuthManager = new RuntimeBeanReference(
parentMgrRef);
authManager.addConstructorArgValue(parentAuthManager);
RootBeanDefinition clearCredentials = new RootBeanDefinition(
ClearCredentialsMethodInvokingFactoryBean.class);
clearCredentials.getPropertyValues().addPropertyValue("targetObject",
parentAuthManager);
clearCredentials.getPropertyValues().addPropertyValue("targetMethod",
"isEraseCredentialsAfterAuthentication");

authManager.addPropertyValue("eraseCredentialsAfterAuthentication",
clearCredentials);
}
else {
RootBeanDefinition amfb = new RootBeanDefinition(
AuthenticationManagerFactoryBean.class);
amfb.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
String amfbId = pc.getReaderContext().generateBeanName(amfb);
pc.registerBeanComponent(new BeanComponentDefinition(amfb, amfbId));
RootBeanDefinition clearCredentials = new RootBeanDefinition(
MethodInvokingFactoryBean.class);
clearCredentials.getPropertyValues().addPropertyValue("targetObject",
new RuntimeBeanReference(amfbId));
clearCredentials.getPropertyValues().addPropertyValue("targetMethod",
"isEraseCredentialsAfterAuthentication");

authManager.addConstructorArgValue(new RuntimeBeanReference(amfbId));
authManager.addPropertyValue("eraseCredentialsAfterAuthentication",
clearCredentials);
}

authManager.getRawBeanDefinition().setSource(pc.extractSource(element));
BeanDefinition authMgrBean = authManager.getBeanDefinition();
String id = pc.getReaderContext().generateBeanName(authMgrBean);
pc.registerBeanComponent(new BeanComponentDefinition(authMgrBean, id));

return new RuntimeBeanReference(id);
}

Ok, 上面的代码到底想完成一个什么样的逻辑呢?实际上主要是判断,用户是否通过 http 元素的属性 authentication-manager-ref 自定义过相关的 Authentication Provider

  1. 如果定义过,那么这里通过参数 authManager 所定义的 ProviderManager 的构造函数将会填充为该自定义的 Authentication Provider;注意,这里不要被属性的名字 authentication-manager-ref 名字所迷惑,它不是让你自己再去定义一个 Authentication Manager,而是让你去自定义一个 Authentication Provider;
  2. 如果没有定义过,那么将会通过系统内置的 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
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
public HttpConfigurationBuilder(Element element, boolean addAllAuth,
ParserContext pc, BeanReference portMapper, BeanReference portResolver,
BeanReference authenticationManager) {
// 1. 初始化赋值给成员变量
this.httpElt = element;
this.addAllAuth = addAllAuth;
this.pc = pc;
this.portMapper = portMapper;
this.portResolver = portResolver;
this.matcherType = MatcherType.fromElement(element);
// 2. 解析并处理子元素 intercept-url
interceptUrls = DomUtils.getChildElementsByTagName(element,
Elements.INTERCEPT_URL);
// 2.1 这里是对每一个 intercpet-url 子元素做验证,如果 intercept-url 包含 filter 属性,则报错
for (Element urlElt : interceptUrls) {
if (StringUtils.hasText(urlElt.getAttribute(ATT_FILTERS))) {
pc.getReaderContext()
.error("The use of \"filters='none'\" is no longer supported. Please define a"
+ " separate <http> element for the pattern you want to exclude and use the attribute"
+ " \"security='none'\".", pc.extractSource(urlElt));
}
}

// 3. session policy
String createSession = element.getAttribute(ATT_CREATE_SESSION);

if (StringUtils.hasText(createSession)) {
sessionPolicy = createPolicy(createSession);
}
else {
sessionPolicy = SessionCreationPolicy.IF_REQUIRED;
}

// 4. 创建所有相关的 filters,这些 filters 至关重要;
createCsrfFilter();
createSecurityContextPersistenceFilter();
createSessionManagementFilters();
createWebAsyncManagerFilter();
createRequestCacheFilter();
createServletApiFilter(authenticationManager);
createJaasApiFilter();
createChannelProcessingFilter();
createFilterSecurityInterceptor(authenticationManager);
createAddHeadersFilter();
}

从整个处理逻辑上来区分,主要包含了四块

  1. 为成员变量进行初始化并赋值;

  2. 解析并处理 http 的子元素 intercept-url,从后续的代码分析可知,这里其实只是遍历所有的 intercept-url,然后做了一个简单的验证,判断该 intercept-url 上是否包含了 filter 属性,如果是,则报错;

  3. 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
    17
    private 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

  4. 创建所有相关的 filters

    1. Csrf Filter
      该过程是通过 CsrfBeanDefinitionParser 解析得到的
    2. SecurityContextPersistenceFilter
      该 Filter 主要是通过创建 HttpSessionSecurityContextRepository 的 bean definitions 来初始化该 Filter 的 bean definitions;备注,HttpSessionSecurityContextRepository 非常关键,它定义了 Http Session 里面的值的存储策略,如果想要将 Http Session 中的值转储到数据库或者分布式缓存中,那么需要对它进行配置;这里不对它展开来描述了;
    3. SessionManagementFilters
      createSessionManagementFilters() 有关的这段方法的代码真没办法一口气对完,一个 function 总共差不多 200 行,而且没什么注释… 吐槽,有时候 Spring 的源码读得真让人头大… 总体而言,就是为了对 session 进行方方面面的控制,而初始化相关的 bean definitions,里面不仅包含对 session 的一系列常规控制的 bean definitions 还包含了 CSRF 相关的东西.. 等将来有时间了,再慢慢啃这里的代码吧..
    4. WebAsyncManagerFilter
    5. RequestCacheFilter
      解析 request-cache 属性
    6. ServletApiFilter
      初始化 SecurityContextHolderAwareRequestFilter 相关的 Bean Definitions
    7. JaasApiFilter
      初始化 JaasApiIntegrationFilter 相关的 Bean Definitions
    8. ChannelProcessingFilter
      初始化 ChannelProcessingFilter 相关的 Bean Definitions,包含与 http、https 以及 retryWithHttp、retryWithHttps 等相关的属性
    9. 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));
    10. AddHeadersFilter
      该 Filter 是通过 HeadersBeanDefinitionParser 进行解析并初始化的,里面通过解析 http 元素及其子元素和相关属性,分别生成与之相关的 CacheControlHeadersWriter、HstsHeaderWriter、XXssProtectionHeaderWriter 等于 HeaderWriter 相关的 Bean definitions

其次,再看上述代码第 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
29
public 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,

  1. AnonymousFilter
    如果配置了 http 的子元素 anonymous,并且 anonymous 没有被禁用的话,那么将初始化 AnonymousAuthenticationFilter 的 bean definition

  2. RememberMeFilter
    解析 <remember-me>子元素,如果配置了 http 的子元素 remember-me,并且 remember-me 没有被禁用的话,会创建与 remember-me 相关的 RememberMeAuthenticationFilter 以及 RememberMeAuthenticationProvider 的 bean definitions;

  3. BasicFilter
    解析 <http-basic> 元素,如果<http> 的子元素 <http-basic>被激活,那么将会初始化与 Http Basic 认证相关的 BasicAuthenticationFilter、BasicAuthenticationEntryPoint 相关的 bean definitions,其中 BasicAuthenticationEntryPoint 可以通过 entry-point-ref 属性自行定义;

  4. FormLoginFilter
    解析 <form-login> 子元素,看看其解析的核心代码,

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    FormLoginBeanDefinitionParser 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;

  5. OpenIDLoginFilter

  6. X509Filter

  7. JeeFilter

  8. 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 是一个不二的选择;

  9. LoginPageFilterIfNeeded

  10. UserDetailsServiceFactory
    这一步就是用来初始化 UserDetailsServiceFactoryBean 的 bean definitions 的;

  11. ExceptionTranslationFilter

authentication-manager

这里将会采用 AuthenticationManagerBeanDefinitionParser 对元素 authentication-manager 进行解析,下面作者就带领代价来读一读这部分的核心代码,

AuthenticationManagerBeanDefinitionParser.java

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
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
public 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 进行解析,该解析过程中,将会解析如下的属性或元素

  1. 解析子元素 password-encoder,解析用户自定义的相关的 password-encoder
  2. 获取子元素 user-service
    若不存在子元素 user-service,则判断是否有有子元素 jdbc-user-service
    若不存在子元素 user-service,则判断是否有有子元素 ldap-user-service
  3. 解析属性 user-service-ref,那么解析该属性,创建用户自定义的 UserDetailsService bean definitiion;
  4. 解析子元素 user-service,根据 user-service 的不同类型对 user-service 进行解析,按照这里使用的 demo 中所使用到的 user-service 的配置,这里解析的过程中将会使用到 InMemoryUserDetailsManager;在解析的过程中,还会继续解析 cache-ref 属性,并创建 CachingUserDetailsService 相关的 bean definition;
  5. 最后,如果没有明确定义使用哪个 authentication-provider 将会默认使用 DaoAuthenticationProvider 并创建其相关的 bean definition;

总结

此步骤主要是根据用户相关的 Spring Security 的配置,在 Spring 容器中初始化相关的 bean definitions;