手动配置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。所以,这些为什么还有价值呢?(译者注:作者的这个反问应该指的是手动配置的意义在哪里。)