手动配置Spring Security 设施的bean
如果你工作要求的环境很复杂而 Spring Security 的基本功能——尽管非常强大——不能满足所有的要求,你可能最终需要自己从头构建 Spring Security 的过滤器链以及支持实施。这是在 Spring Security 参考手册中没有完全提及的部分,但是却难住了很多人。有些人将这种类型的配置成为 Spring Security 的“另一个宇宙( alternate universe )”。
自己构建并织入所有需要的 bean ,将为你提供很高的灵活性和自定义功能,这是通过 security 命名空间的 <http> 风格配置所不允许的。
【我们不是开玩笑地说构建所需的 bean 是很复杂的。即使一个必须的 bean 都可能需要多达 25 个单独的 bean 。,这需要开发人员明确理解 bean 之间的依赖关系以及每个 bean 的所有属性。记住,一旦你不再使用基于 XML 命名空间的便利配置方式,就会与 Spring Security 代码和整体结构更密切相关了。 Security XML 命名空间提供了一层很受欢迎的抽象并能很好地满足大多数的需求。】
希望到本书的这个地方,你已经理解了请求处理架构和相关的主要组件,这样的话,面对大量需要配置的 bean 才不会感到吃惊。完全理解表面下所有的组件,对于配置每个 bean 来说是很有用的。
我们将会花些时间来介绍自己搭建 Spring Security 基础设施时所需要的主要组成部分。注意的是在有些场景下,当没有必要进行阐述时,我们将会省略一些没有意思 bean 的细节;但是,完整的配置文件(在本章源码中的 dogstore-explicit-base.xml 中)需要支持它。现在让我们进入主要的配置过程。
总体理解Spring Security bean 的依赖关系
我们不想马上扎入到配置 bean 中,而是要给你一个总体了解手动建立 Spring Security bean 时所涉及的主要组件。以下是一个依赖图展现了我们要配置的 bean 以及它们怎样交互的(这个图在本章的源码中包含完整尺寸的,作为参考)。
需要记住的是,这个图中包含的 bean 超过了是系统启动和运行所需要的最少值。我们将会渐进的阐述怎样添加所有的 bean ,从最小的集合开始,并逐渐构建出与用 security 命名空间相匹配的功能。
重新配置web 应用
为了表述清晰,我们为这种风格的配置创建一个全新的 XML 配置文件。这样我们能够很清晰地看到什么才是明确需要的,并移除我们在前面章节中注释过的和没注释过的一些部分。
为了做到这些,我们需要重新配置 Spring 的 ApplicationContext 指向这个新的文件。我们将这个文件命名为 dogstore-explicit-base.xml ,并更新 web.xml 文件指向它,如下: <web-app ...>
- < display-name > Dog Store </ display-name >
- < context-param >
- < param-name > contextConfigLocation </ param-name >
- < param-value >
- /WEB-INF/dogstore-explicit-base.xml
- </ param-value >
- </ context-param >
我们不再需要单独 dogstore-security.xml 文件了,这个文件是我们为了使用 XML security 命名空间声明而创建的。我们大多数的配置将会使用 Spring 标准的 bean 注入语法,但有少量的 security 命名空间装饰器在里面。
配置一个最小的Spring Security 环境
我们将会以能使系统重新运行起来的最小的配置开始——这意味着没有 remember me 、 logout 以及异常处理功能。这使得我们可以聚焦于 Spring Security 启动的最小需求。
首先,我们需要声明 Spring Security 拦截请求时所使用的 servlet 过滤器链,如下:
- < bean id = "springSecurityFilterChain"
- class = "org.springframework.security.web.FilterChainProxy" >
- < security:filter-chain-map path-type = "ant" >
- < security:filter-chain pattern = "/**" filters ="
- securityContextPersistenceFilter,
- usernamePasswordAuthenticationFilter,
- anonymousAuthenticationFilter,
- filterSecurityInterceptor" />
- </ security:filter-chain-map >
- </ bean >
你可以看到这里我们已经引用了 security 命名空间。尽管可以使用手动的方式来配置需要 bean 的属性以建立路径模式匹配和过滤器列表组合,但是使用 security 命名空间的 filter-chain-map 包装器更简便和便利。如果将这与 <http> 风格的配置进行对比的话,我们要注意以下的配置元素:
l 默认过滤器链的建立是在处理 <http> 元素的时候自动包含的并不需要直接配置。尽管使用 security 命名空间的 <custom-filter> 重写或扩展标准过滤器链的时候,允许很大程度的灵活性,但它并不能够得到 FilterChainProxy 本身。
l 基于 URL 模式修改过滤器连并不适用于 <http> 风格的声明。如果应用的某些部分不需要特定的处理这将会有用处,并且能使得过滤器的调用尽可能得少。
需要意识到很重要的一点是,不同于 Spring 的一些配置(比较明显的如,在 web.xml 中的 contextConfigLocation ),在过滤器的名字之间需要需要使用逗号分隔。
【过滤器的顺序很重要——正如我们在第二章中所讨论的那样,特定的过滤器必须在另一些的前面。除 非你有特殊的需求,请参考第二章中的表格,当你添加标准的过滤器到一个手动配置的过滤器链中时,要保证它们在合适的位置。过滤器被包含在不正确的位置可能 会导致不可预知的应用行为,而这是很难调试的。】
你会意识到 <filter-chain> 元素引用了很多逻辑 bean definitions ,而它们还没有进行定义。现在对它们进行定义,并逐个进行详细介绍。
配置最少的 servlet 过滤器集合
为了支持上面描述的过滤器链,我们要设置两类的对象。
首先, servlet 过滤器本身必要要进行设置。它们定义了进入 web 应用的用户请求是如何处理的——与使用 security 命名空间不同的是我们更接近底层本质,并需要明确定义在以前简单配置时隐藏在背后的过滤器。
其次, servlet 过滤器依赖一系列提供支持功能的安全基础设施 bean 。它们中的一些类对你来说可能比较熟悉,因为我们从第一章到第五章已经从架构和功能性的视角讲到了它们,但是这些 bean 的配置方式是全新的。
我们将从需要的过滤器开始。
SecurityContextPersistenceFilter
SecurityContextPersistenceFilter 用来建立 SecurityContext ,而它被用来贯穿整个 request 过程以跟踪请求者的认证信息。你可能记得我们在上一章的 Spring MVC 代码中,为了得到当前认证过的 Principa 时,访问过 SecurityContext 对象。
包含默认适当 web session 管理的 SecurityContextPersistenceFilter 基本配置如下:
- < bean id = "securityContextPersistenceFilter"
- class ="org.springframework.security.web.context .SecurityContextPersistenceFilter />
在这个场景背后有许多的东西,我们可以根据要求使这个配置很复杂,如 HTTP session 如何管理。现在,我们使这个过滤器采用默认设置然后继续,但是在本章的后面部分我们将会涉及 session 相关的配置选项。
UsernamePasswordAuthenticationFilter
正如我们在第二章中详细介绍的那样, UsernamePasswordAuthenticationFilter 用来处理 form 提交并检查认证存储是否为合法凭证。明确配置这个过滤器,对比 security 命名空间的配置,如下:
- < bean id = "UsernamePasswordAuthenticationFilter"
- class ="org.springframework.security.web
- .authentication.UsernamePasswordAuthenticationFilter">
- < property name = "authenticationManager" ref = "customAuthenticationManager" />
- </ bean >
如果你有时间深入研究这个类你会发现它有一个范式(译者注:原文为 pattern ,个人理解是用户名、密码、 url 等配置)——其实这里还有更多的可配置内容。同样的,在基本配置能够运行后,我们将会再次介绍它。我们引用了一个名为 customAuthenticationManager 的 bean ——这就是与使用 security 命名空间的 <authentication-manager> 元素自动配置相同的 AuthenticationManager 。我们稍后将会配置这个 bean 。
AnonymousAuthenticationFilter
我们的站点允许匿名访问。尽管对于比较特殊的条件 AnonymousAuthenticationFilter 并不需要,但是通常情况下会使用它,因为只对请求添加了一点的预处理。你可能并不认识这个过滤器,除了我们在第二章对其简短提到以外。这是因为对于 AnonymousAuthenticationFilter 的配置都掩盖在 security 命名空间之中。
这个过滤器的最小配置如下:
- < bean id = "anonymousAuthenticationFilter"
- class ="org.springframework.security.web
- .authentication.AnonymousAuthenticationFilter">
- < property name = "userAttribute"
- value = "anonymousUser,ROLE_ANONYMOUS" />
- < property name = "key" value = "BF93JFJ091N00Q7HF" />
- </ bean >
列出的这两个属性都是需要的。 userAttribute 属性声明了为匿名用户提供的用户名和 GrantedAuthority 。用户名和 GrantedAuthority 可能在我们的应用中原来验证用户是不是匿名用户。 Key 可能是随机生成的,但是需要在一个 bean 中使用( o.s.s.authentication.AnonymousAuthenticationProvider ),我们稍后将会进行配置。
FilterSecurityInterceptor
在我们基本处理过滤器链的最后一个是最终负责检查 Authentication 的,而这是前面已配置的安全过滤器的处理结果。正是这个过滤器确定一个特定的请求最终是被拒绝还是被接受。
让我们看一下这个过滤器的配置并了解这个练习与其对应的 security 命名空间进行比较。
- < bean id = "filterSecurityInterceptor"
- class = "org.springframework.security.web.access .intercept.FilterSecurityInterceptor" >
- < property name = "authenticationManager" ref = "customAuthenticationManager" />
- < property name = "accessDecisionManager" ref = "affirmativeBased" />
- < property name = "securityMetadataSource" >
- < security:filter-security-metadata-source >
- < security:intercept-url pattern = "/login.do"
- access = "IS_AUTHENTICATED_ANONYMOUSLY" />
- < security:intercept-url pattern ="/
- home.do" access = "IS_AUTHENTICATED_ANONYMOUSLY" />
- < security:intercept-url pattern ="/
- account/*.do" access = "ROLE_USER" />
- < security:intercept-url pattern = "/*" access = "ROLE_USER" />
- </ security:filter-security-metadata-source >
- </ property >
- </ bean >
在继续阅读之前请思考一下。没错,它看起来和我们在使用 security 命名空间的 <http> 配置的 <intercept-url> 声明一样。模式匹配和访问声明格式完全一致,但是你会发现在本例中我们使用了一些配置魔法来使用相同的声明在上下文中建立正常的 Spring bean 属性。
<filter-security-metadata-source> 元素负责配置 FilterSecurityInterceptor 会用到的 SecurityMetadataSource 的实现,包含 URL 声明以及可以访问所需要的角色。
【有效使用 XML 命名空间。对于不熟悉 Spring 高级配置的用户来说到这里通常会比较迷惑。在配置文件中, Spring XML 配置有效使用了 XML 命名空间来提供清晰的基于组件所有的不同元素。标示元素在一个命名空间中需要以一个冒号( : )做前缀,后面跟着元素的名字。所以如果我们看 <security:intercept-url> ,我们可以看到这个元素的名字是 intercept-url ,并在名为 security 的 XML 命名空间里。 XML 命名空间的通常在 XML 文件的顶部声明,带有一个任意的前缀设置并关联一个 URI 。例如, security 命名空间可以声明为 xmlns:security=http://www.springframework.org/schema/security 。没有声明命名空间的元素呢?它们将会被分配为 XML 文档的默认命名空间。默认的命名空间通过 xmlns 属性来标示——在 dogstore-explicit-base.xml 文件中,默认的命名空间被声明为 xmlns="http://www.springframework.org/schema/beans" 。没有命名空间前缀的元素将会被自动设置为默认的命名空间。理解 XML 命名空间实际如何运行将会使你在构建复杂 Spring 和 Spring Security 配置文件时免去很多头疼之苦。】
我们已经为明确设置的最小化过滤器链配置完了所有过滤器。这些过滤器不能独立存在——还有几个需要的支持对象。
配置最少的支持对象集合
为了建立最小配置所需要的支持对象是我们在前面的章节中已经配置过的(除了一个) Spring bean ——所以我们将会节省一些时间来介绍它们的用处。
以下为一系列的 bean 定义,它们是为了完成最小的支持对象集合和启动应用的:
- < bean class = "org.springframework.security.access.vote.AffirmativeBased" id = "affirmativeBased" >
- < property name = "decisionVoters" >
- < list >
- < ref bean = "roleVoter" />
- < ref bean = "authenticatedVoter" />
- </ list >
- </ property >
- </ bean >
- < bean class = "org.springframework.security.access .vote.RoleVoter" id = "roleVoter" />
- < bean class = "org.springframework.security.access.vote.AuthenticatedVoter"
- id = "authenticatedVoter" />
- < bean id = "daoAuthenticationProvider"
- class = "org.springframework .security.authentication.dao.DaoAuthenticationProvider" >
- < property name = "userDetailsService" ref = "jdbcUserService" />
- </ bean >
- < bean id =”anonymousAuthenticationProvider”
- class =”org.springframework.security.authentication.AnonymousAuthenticationProvider” >
- < property name =”key” value =”BF93JFJ091N00Q7HF” />
- </ bean >
这个配置为我们提供了一个最小的配置支持匿名浏览、登录以及数据库后台的认证(注意的是为了简洁我们省略了需要的 jdbcUserService 和 dataSource beans ——这些 bean 的定义与前面的定义没有什么变化)。记住, AnonymousAuthenticationProvider 的 key 属性必须与我们前面定义的 AnonymousAuthenticationFilter 的 key 属性相匹配。
有一个 bean 需要,但是我们前面从来没有配置过的(这是因为 security 命名空间配置不允许这样做)就是 AuthenticationManager 。我们可以定义这个 bean 如下:
- < bean id = "customAuthenticationManager"
- class = "org.springframework .security.authentication.ProviderManager" >
- < property name = "providers" >
- < list >
- < ref local = "daoAuthenticationProvider" />
- < ref local =”anonymousAuthenticationProvider” />
- </ list >
- </ property >
- </ bean >
AuthenticationManager 的配置在这里看起来很简单,但是明确配置这个 bean 是很多有用增强和扩展框架的关键,这些我们将会在本章的剩余部分讲解。
在手动配置 bean 的所有工作完成后,我们的站点依旧不支持我们用 security 配置时的一些功能,包括退出功能、友好的异常处理、密码 salting 以及 remember me 。所以,这些为什么还有价值呢?(译者注:作者的这个反问应该指的是手动配置的意义在哪里。)