《Spring Security3》第六章第六部分翻译(Spring Security基于bean的高级配置)
Spring Security基于bean的高级配置
正如我们在前面几页中看到的那样,基于bean的Spring Security配置尽管比较复杂,但是提供了一定层次的灵活性,如果复杂应用需要超过security XML命名空间风格配置所允许的功能时会用到。
我们将利用这个章节来阐明可用的一些配置选项以及怎么使用。尽管我们不能提供每个可能属性的细节,但是我们鼓励基于在本章和以前章节学到的内容你开始探索并查询Javadoc和Spring Security的源码。你仅仅会受限于对这个框架是如何组织起来的知识和你的想象力。
Session生命周期的调整元素
Spring Security有很多地方影响用户的HttpSession的生命周期。有很多功能只有将相关类配置成Spring bean时才可用。以下的表格列出了能够影响session创建和销毁的bean属性:
Class(类) |
属性 |
默认值 |
描述 |
AbstractAuthentication ProcessingFilter (UsernamePasswordAuthenticationFilter的父类) |
allowSessionCreation |
true |
如果为true,当认证失败时创建一个新的session(存储异常) |
UsernamePasswordAuthenticationFilter |
allowSessionCreation |
true |
如果为true的话,这个特殊的过滤器将会创建一个session存储最后尝试的用户名。 |
SecurityContextLogoutHandler |
invalidateHttpSession |
true |
如果为true,HttpSession将会失效(参考Servlet规范了解session失效的细节) |
SecurityContextPersistenceFilter |
forceEagerSessionCreation |
false |
如果为true,该过滤器将会在执行链中其它过滤器之前创建一个session。 |
HttpSessionSecurityContextRepository |
allowSessionCreation |
true |
如果为true,如果在请求结束时session中还没有SecurityContext 的话,SecurityContext将存储到session中。 |
取决于你应用的需要,需要审慎分析用户session——包括认证的和非认证的——并调整相应的session生命周期。
手动配置其它通用的服务
还有一些其它的服务,我们已经在security命名空间自动配置中使用了。让我们将其添加上,这样就会有一个完整的基本配置实现。
声明缺失的过滤器
我们将要增强手动配置的过滤器链,添加三个我们还没有配置的服务。包含处理退出、remember me以及异常转换。一旦我们完成这些过滤器,我们将会有完整的功能并可以在它启动时深入了解一些有趣的配置选项。
以下缺失的过滤器将会添加到我们的过滤器链中:
<bean id="springSecurityFilterChain" class="org.springframework.security.web.FilterChainProxy"> <security:filter-chain-map path-type="ant"> <security:filter-chain pattern="/**" filters=" securityContextPersistenceFilter, logoutFilter, usernamePasswordAuthenticationFilter, rememberMeAuthenticationFilter, anonymousAuthenticationFilter, exceptionTranslationFilter, filterSecurityInterceptor" /> </security:filter-chain-map> </bean>
正如在前面做的那样,现在我们需要声明这些Spring bean并配置他们以及所有依赖的服务bean。
LogoutFilter
LogoutFilter的基本配置(它默认用来响应虚拟URL /j_spring_security_logout)如下:
<bean id="logoutFilter" class="org.springframework.security .web.authentication.logout.LogoutFilter"> <!-- the post-logout destination --> <constructor-arg value="/"/> <constructor-arg> <array> <ref local="logoutHandler"/> </array> </constructor-arg> <property name="filterProcessesUrl" value="/logout"/> </bean>
你会发现构造LogoutFilter的方法与其它的认证过滤器都不一样,使用了构造方法而不是bean属性。第一个构造参数是用户退出后要转向的URL,第二个参数是对LogoutHandler实例的引用。
【依赖注入中的构造器哲学。如果你使用Spring有一段时间了,可能已经讨论过以适当的面向对象方式管理需要的依赖。尽管setter方式对Spring的用户来说很便利,但是追求纯正面向对象的人经常会认为如果一个依赖影响到类的功能,那它应该是构造方法签名的一部分(思考一下在Spring以前你是怎样写类的)。鉴于Spring Security已经发展了有些年头,不同的作者对此有不同的观点,而我们碰巧正好遇到一个或两个这方面的不一致。两种风格的依赖模型都可以——到底使用哪种风格其实取决于你喜欢哪种类型的需求依赖模型。】
我们还要声明一个简单的LogoutHandler的如下:
<bean id="logoutHandler" class="org.springframework.security .web.authentication.logout.SecurityContextLogoutHandler"/>
你可能认出这个LogoutHandler在我们前几页关于session处理的表格中出现过。一个明确配置logout处理器的好处就是你能修改用户退出时默认的session处理行为。一旦我们完成这两点的配置,Log Out链接就能够再次开始工作了。
请记住在第三章security命名空间配置中,我们曾经修改应用使用一个不那么明显绑定Spring Security的退出URL。当我们从security命名空间配置方式修改为明确bean定义时,这是我们在这个bean定义时重写filterProcessesUrl属性的原因,以保持应用的配置保持持续性。
RememberMeAuthenticationFilter
你可能也会回忆起在第三章中使用的remember me功能。即使在基于bean的配置中,我们也要保持这个有用的remember me功能。这关系到一个新的过滤器、新的支持bean以及修改一些其它的bean。首先让我们看一下这个过滤器:
<bean id="rememberMeAuthenticationFilter" class="org.springframework.security.web .authentication.rememberme.RememberMeAuthenticationFilter"> <property name="rememberMeServices" ref="rememberMeServices"/> <property name="authenticationManager" ref="customAuthenticationManager" /> </bean>
你会发现对rememberMeServices的引用,而它还没有定义。现在让我们定义这个bean:
<bean id="rememberMeServices" class="org.springframework.security.web.authentication .rememberme.PersistentTokenBasedRememberMeServices"> <property name="key" value="jbcpPetStore"/> <property name="tokenValiditySeconds" value="3600"/> <property name="tokenRepository" ref="jdbcRememberMeTokenRepository"/> <property name="userDetailsService" ref="jdbcUserService"/> </bean>
RememberMeServices实现的很多配置属性与security命名空间风格的配置很相似。这两种方法的最大不同是RememberMeServices从应用配置的细节中抽象出来了(译者注:即在security方式的配置中,已经进行了一些抽象),但是在基于bean的配置中,bean声明必须非常了解在remember me功能中所关联的所有bean。如果你感到迷惑,请回顾第三章到第四章以了解remember me功能所关联的类和流程的细节。
我们有另一个bean应用来织入remember me的token存储。这个bean定义很简单,如下:
<bean id="jdbcRememberMeTokenRepository" class="org.springframework.security.web .authentication.rememberme.JdbcTokenRepositoryImpl"> <property name="dataSource" ref="dataSource"/> </bean>
最后我们需要声明AuthenticationProvider,它负责处理remember me认证请求。它的声明如下:
<bean id="rememberMeAuthenticationProvider" class="org.springframework.security .authentication.RememberMeAuthenticationProvider"> <property name="key" value="jbcpPetStore"/> </bean>
你可能会记得这个key属性在AuthenticationProvider(用来进行token校验)和RememberMeServices(用来进行token生成)之间共享。要保证它们被设置成相同的。你可能想通过一个properties文件来设置这个属性值,那是要PropertyPlaceholderConfigurer工具类(或其它类似的)。
现在我们需要将这个AuthenticationProvider织入到AuthenticationManager的列表中。
<bean id="customAuthenticationManager" class="org.springframework.security .authentication.ProviderManager"> <property name="providers"> <list> <ref local="daoAuthenticationProvider"/> <ref local=”anonymousAuthenticationProvider”/> <ref local="rememberMeAuthenticationProvider"/> </list> </property> </bean>
RememberMeServices必须织入到UsernamePasswordAuthenticationFilter,这样RememberMeServices就能处理明确的登录成功(remember me cookie被更新)和失败(remember me被移除)。(译者注:其实查看源码在登录失败时,没有移除cookie的操作)
<bean id="usernamePasswordAuthenticationFilter" class="org.springframework.security.web .authentication.UsernamePasswordAuthenticationFilter"> <property name="authenticationManager" ref="customAuthenticationManager"/> <property name="rememberMeServices" ref="rememberMeServices"/> </bean>
最后要记住的一件事(在配置Spring Security bean声明时,用户经常遗忘)RememberMeServices也是LogoutHandler功能的一部分,它在用户退出时清理用户的cookie。
<bean id="logoutFilter" class="org.springframework.security.web .authentication.logout.LogoutFilter"> <constructor-arg value="/"/> <constructor-arg> <array> <ref local="logoutHandler"/> <ref local="rememberMeServices"/> </array> </constructor-arg> <property name="filterProcessesUrl" value="/logout"/> </bean>
当这些配置到位,我们就完成了明确织入remember me功能。你可能没有想到过<remember-me>声明在幕后做了多少的工作。
ExceptionTranslationFilter
在Spring Security标准的过滤器链中最后一个servlet过滤器是ExceptionTranslationFilter。从本章我们前面的讨论,回忆一下这个过滤器在认证和授权整个流程中的重要性。最后的这个过滤器需要一个过滤器定义和两个支持bean。
<bean id="exceptionTranslationFilter" class="org.springframework.security.web .access.ExceptionTranslationFilter"> <property name="authenticationEntryPoint" ref="authenticationEntryPoint"/> <property name="accessDeniedHandler" ref="accessDeniedHandler"/> </bean>
支持bean的声明如下:
<bean id="authenticationEntryPoint" class="org.springframework.security.web .authentication.LoginUrlAuthenticationEntryPoint"> <property name="loginFormUrl" value="/login.do"/> </bean> <bean id="accessDeniedHandler" class="org.springframework.security.web .access.AccessDeniedHandlerImpl"> <property name="errorPage" value="/accessDenied.do"/> </bean>
你可能会认出errorPage指令定义的Access Denied页面就是本章前面访问拒绝配置练习中的那个地址。类似的,loginFormUrl对应于我们在前面看到的security命名空间中的login-page属性。
明确配置SpEL表达式和投票器
让我们看一下与security命名空间中use-expressions="true"属性等同的Spring bean配置:
<bean class="org.springframework.security .web.access.expression.DefaultWebSecurityExpressionHandler" id="expressionHandler"/>
接下来,我们需要建立投票器(Voter)指向表达式处理器,如下:
<bean class="org.springframework.security.web.access .expression.WebExpressionVoter" id="expressionVoter"> <property name="expressionHandler" ref="expressionHandler"/> </bean>
最后,我们需要使AccessDecisionManager bean使用这个投票器。
<bean class="org.springframework.security.access.vote.AffirmativeBased" id="affirmativeBased"> <property name="decisionVoters"> <list> <ref bean="expressionVoter"/> </list> </property> </bean>
在完成这些配置以后,我们回到了使用use-expressions="true"默认设置的那个点上(译者注:即与使用命名空间配置use-expressions="true"一样的效果)。但是,既然我们使用了明确配置方式,那么可以使用自定义的类替换默认的某个或全部类。我们将会本章后面的部分看一个这样的例子。
基于bean配置方法安全
在第五章中,我们使用security命名空间<global-method-security>声明的方式配置过方法安全。迁移到明确的、基于bean的配置时,我们必须配置需要的主要类和支持类以重写<global-method-security>声明的功能。
我们在第五章中,已经开发出完整的以bean配置的方法安全,支持各种类型的注解。鉴于这个配置很简单直接,基本上没有什么有意思的属性,所以我们将完整的配置放在附录:参考资料中以及本章的dogstore-explicit-base.xml配置文件里面。
包装明确的配置
到此时,我们已经完成了配置基于bean的Spring security,应用也是功能完整的了。如果你想体验security命名空间风格的配置与bean声明的配置,很容易修改web.xml中对Spring配置文件的引用从而把一系列配置文件的集合切换到另一个。
l security命名空间的配置文件为web.xml引用的dogstore-base.xml和dogstore-security.xml;
l 基于bean的配置文件为web.xml引用的dogstore-explicit-base.xml。
从此往后,本书的练习中将会假设你使用的是security命名空间配置。如果特定的功能只能使用基于bean的配置,我们将会明确说明。我们也会包含一些基于bean配置的细节,因为我们知道一些开发人员更愿意拥有这个级别上对安全框架的控制。
我们应该选择什么类型的配置呢?
希望你的大脑没有从我们刚才的所有配置中变迷糊。你可能会想知道,对于一个典型的工程,应该选择什么类型的配置呢。正如你可能预料的那样,答案是取决于你项目的复杂性以及你重写或自定义Spring Security元素的程度。
配置类型 |
好处 |
security命名空间 |
l 强大、简洁语法,适用于通常的web和方法安全配置; l 用户配置复杂功能时,并不需要知道组件在幕后是如何交互的; l security命名空间进行代码检查和并警告多种潜在的配置缺陷; l 明显减少缺失配置步骤的可能性。 |
明确的bean定义 |
l 允许最大灵活性以扩展、重写以及删节标准的Spring Security功能; l 允许自定义过滤器链及根据URL模式(使用<filter-chain>元素的pattern属性)的认证方法。这可能在混合web service或REST认证以及用户认证时用到; l 不用直接将配置文件绑定到Spring Security命名空间处理上; l 认证管理器可以明确配置或重写; l 与更简单的security命名空间相比,暴露了有更多的配置选项。 |
对于大多数项目,比较明智的是以security命名空间开始,并在可能的情况下继续使用直到你的应用需要它不满足的功能。要记住的是,自己配置所有需要的bean和bean间的依赖关系将会明显增加复杂度,在开始之前,你应该对Spring Security的结构(可能还包括底层代码)有清晰的理解。
推荐阅读
-
《Spring Security3》第六章第二部分翻译(自定义AuthenticationProvider)
-
《Spring Security3》第六章第四部分翻译(异常处理)
-
《Spring Security3》第四章第三部分翻译上(配置安全的密码)
-
《Spring Security3》第六章第二部分翻译(自定义AuthenticationProvider)
-
《Spring Security3》第四章第二部分翻译(JdbcDaoImpl的高级配置)
-
《Spring Security3》第六章第一部分翻译(自定义安全过滤器)
-
《Spring Security3》第五章第四部分翻译(方法安全的高级知识和小结)
-
《Spring Security3》第六章第三部分翻译(Session的管理和并发)
-
《Spring Security3》第六章第七部分翻译(认证事件处理与小结)
-
《Spring Security3》第六章第五部分翻译(手动配置Spring Security设施的bean)