第二章
安全命名空间配置
2.1. 简介 Spring框架2.0版本以后开始支持命名空间的配置。它允许你使用额外的XML schema的元素来补充传统的Spring bean应用上下文的语法。你可以在Spring的参考文档中发现更多信息。一个命名空间的元素可以方便地使用更简洁的方式配置bean,更为强大的是,它可以定义可供选择的配置语法,这些语法更贴近问题领域和隐藏用户的复杂性。一个简单的元素可能隐藏着多个bean以及将要添加到应用上下文的处理步骤。举例来说,把下面安全命名空间的元素添加到应用上下文中,将会开启应用中用于测试的嵌入式LDAP服务器。 这比起写等价的Apache Directory Server beans来说简洁得多。最常见替代配置方式的要求是支持ldap-server元素的属性以及用户能够区分需要配置哪些beans和bean的属性名称。使用一个良好的XML编辑器,来编辑应用上下文中可提供的属性信息和可利用的元素。 我们将推荐您尝试使用SpringSource的开发工具套件,因为它对Spring portfolio命名空间的使用具有特别的功能。 在你的应用上下文中开始使用安全命名空间,你所需要做的是添加schema声明到应用上下文中: 在许多例子的应用中,我们通常会使用"security"作为默认的名字空间,而不是使用"beans",这意味着我们可以省略所有安全命名空间元素的前缀,使上下文更易于阅读。 你也可能这样做,将你的应用上下文内容分割到几个文件中,将你大多数的安全配置放在一个配置文件中。你的安全应用上下文文件可能以这样开始:
beans:beans xmlns="http://www.springframework.org/schema/security" xmlns:beans="http://www.springframework.org/schema/beans"> ... </beans:beans>
|
从现在起,在这一章中我们将采用这一语法。 2.1.1. 命名空间的设计 命名空间的设计是用于实现最常见的用途,并在应用中使用命名空间提供的简单明了的语法。设计很大程度上依赖于框架,它可划分为以下几个方面:
• Web/HTTP Security – 最复杂的部分。设置过滤器和相关用于应用框架认证机制的业务bean,以确保URL安全、提供登录和返回错误页面等更多的工作。 • Business Object (Method) Security – 确保业务层安全的选项。 • AuthenticationManager – 处理来自框架内其他部分的认证请求。 • AccessDecisionManager – 提供适用于Web及方法安全的访问决策。默认的AccessDecisionManager将被注册到应用上下文,但是你也可以自定义一个AccessDecisionManager,并使用传统的Spring bean的语法。
• AuthenticationProviders – 通过认证管理器来验证用户的机制。该命名空间支持一些标准的选项,它也支持通过自定义的bean来使用传统语法的方式。 • UserDetailsService – 与认证提供者紧密相关,它往往也是其他bean所必需的属性。 在下面的各节中,我们将看到如何一起使用它们。 2.2. 安全命名空间配置入门 在本节中,我们将看到如何通过建立一个命名空间的配置来使用框架内的一些主要功能。让我们假设您最初的想法是建立一个尽可能快速运行的,增加认证支持和访问控制的web应用,并在应用中伴随一系列的测试。然后我们将看到如何通过数据库或其他安全信息库来应对认证的改变。在以后的章节中,我们将会介绍更多高级的命名空间配置用法。 2.2.1. web.xml 的配置 你所需要做的第一件事是在web.xml中添加如下的过滤器: <filter> <filter-name>springSecurityFilterChain</filter-name> <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> </filter> <filter-mapping> <filter-name>springSecurityFilterChain</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
|
这提供了一个嵌入到Spring Security的Web架构..然后您开始准备编辑您的应用上下文文件。Web安全服务通过<http>元素来进行配置。 2.2.2. 最简单的<http>配置 你所需要确保web安全应以如下方式开始: <http auto-config='true'> <intercept-url pattern="/**" access="ROLE_USER" /> </http>
|
这就是说我们想让应用中的所有的URL都被保护,只有拥有ROLE_USER权限才能访问他们。 Note 你可以使用多个<intercept-url>元素来定义不同的URL访问策略,但是他们也会对顺序列表进行评估,第一个被匹配的元素将被使用。因此你尽量把最可能匹配的元素放在上面。 你可以直接在命名空间中定义一组测试数据,来添加一些用户: <authentication-provider> <user-service> <user name="jimi" password="jimispassword" authorities="ROLE_USER, ROLE_ADMIN" /> <user name="bob" password="bobspassword" authorities="ROLE_USER" /> </user-service> </authentication-provider>
|
如果你熟悉框架的先前版本,你可能已经大致猜到接下来要做什么。<http>元素负责创建FilterChainProxy和它所使用的过滤器bean。共同的问题类似于过滤器的顺序问题将不再是一个问题了,因为过滤器的位置已经被预定义好了。<authentication-provider>元素创建了一个DaoAuthenticationProviderbean,<user-service>元素创建了一个InMemoryDaoImpl。一个ProviderManager bean总是由命名空间来创建,DaoAuthenticationProvider则伴随着ProviderManager的创建而自动注册。
|
上面的配置定义了2个用户,包括他们的密码和系统中的角色(将被用于访问控制)。在<user-service>中也可以通过使用properties属性的标准属性文件来加载用户信息。查看8.2.1小节可以获取更多详细信息。使用<authentication-provider>元素意味着,用户信息将通过authentication manager来处理认证请求。 在这一点上,你应该启动你的应用程序,把需要的处理过程记录在日志中。尝试一下吧,或者尝试使用Spring Security框架自带的"tutorial"中的例子。上述的配置,实际上在应用中增加了很少的服务,因为我们已经使用了自动配置的属性。举例来说,表单登陆过程和"remember-me"服务是自动启用的。 2.2.2.1.自动配置包含了什么? 自动配置属性,正如下面我们所使用的,是一个简写的语法: <http> <intercept-url pattern="/**" access="ROLE_USER" /> <form-login /> <anonymous /> <http-basic /> <logout /> <remember-me /> </http>
|
其余元素分别负责处理form-login、匿名认证、基本认证、退出处理和remember-me服务。这些元素都有改变自身行为的属性。 2.2.2.2.表单和基本的登陆选项 当您想登陆的时候,您可能想知道登录的表单在哪里,因为我们没有提及任何的HTML和JSP文件。事实上,如果我们没有定义登录页面的URL,Spring Security会自动地生成一个,它含有基本的功能、使用标准的值为URL处理提交登陆、用户向默认的目标URL发送消息等等。然而,命名空间对允许你自定义这些选项提供了大量的支持。举例来说,如果你想提供自己的登录页面,你可以使用: <http auto-config='true'> <intercept-url pattern="/login.jsp*" filters="none"/> <intercept-url pattern="/**" access="ROLE_USER" /> <form-login login-page='/login.jsp'/> </http
|
注意,您仍然可以使用自动配置。form-login元素只覆盖默认的设置。同时也注意到我们增加了一个额外的intercept-url元素来说明任何登陆页面的请求应该由安全过滤器处理。另外,这些请求由/**模式来匹配的话,它便不可能进入自身的登录页面!如果你想用基本的认证替代表单登陆,可以这样改变配置
<http auto-config='true'> <intercept-url pattern="/**" access="ROLE_USER" /> <http-basic /> </http>
|
基本的认证将会优先处理,当用户尝试访问被保护的资源时,它将用于提示用户登录。如果你想使用表单登录的话,它仍然可以使用,例如通过一个登录表单嵌入到别的web页面。
2.2.3. 使用其它的Authentication Providers 在实践中,你将需要一个更具扩展性的用户信息来源,而不是在应用上下文中添加一些名字。最有可能的是,你将你的用户信息存储在数据库中或者是一个LDAP服务器中。LDAP命名空间配置的介绍在LDAP那一章,因为词在这里我们不介绍它。如果在你的应用上下文中,有一个叫做"myUserDetailsService"的bean,它自定义实现Spring Security的UserDetailsService服务,你可以使用如下的方式来进行验证。 <authentication-provider user-service-ref='myUserDetailsService'/>
|
如果你想使用数据库,那么你可以这样做 <authentication-provider> <jdbc-user-service data-source-ref="securityDataSource"/> </authentication-provider>
|
在应用上下文中"securityDataSource"是DataSource bean的名称,它引用了包含标准Spring Security用户数据表的数据库。另外,你也可以配置一个Spring Security的 JdbcDaoImpl bean,并且在user-service-ref属性中引用它。 2.2.3.1. 增加一个密码编码器 通常你的密码通过哈希算法进行加密。在框架中我们使用<password-encoder>元素来给密码进行加密。通过SHA算法来加密,原来的authentication provider的配置可能是这样的: <authentication-provider> <password-encoder hash="sha"/> <user-service> <user name="jimi" password="d7e6351eaa13189a5a3641bab846c8e8c69ba39f" authorities="ROLE_USER, ROLE_ADMIN" /> <user name="bob" password="4e7421b1b8765d8f9406d87e7cc6aa784c4ab97f" authorities="ROLE_USER" /> </user-service> </authentication-provider>
|
当你使用哈希算法加密时,使用密钥来保护密码防止字典攻击是一个不错的主意,并且Spring Security也支持这种方式。最理想的情况,你可以为每一个用户提供一个随机生成的密钥,但是你也可以使用UserDetails对象的任何属性来加载你自己的UserDetailsService。例如,你可以这样使用username属性。 <password-encoder hash="sha"> <salt-source user-property="username"/> </password-encoder>
|
你可以通过引用password-encoder属性来自定义加密算法的bean。在应用上下文中应该包含这个bean,它用于替代Spring Security的加密算法的接口。 2.3. 高级的Web特性 2.3.1. 增加HTTP/HTTPS信道安全 如果你的应用程序既支持HTTP协议又支持HTTPS协议,并且一些特殊的网址只能通过HTTPS协议访问,那么在<intercept-url>元素中直接使用requires-channel属性即可。 <http> <intercept-url pattern="/secure/**" access="ROLE_USER" requires-channel="https"/> <intercept-url pattern="/**" access="ROLE_USER" requires-channel="any"/> ... </http>
|
在上面的配置中,如果用户尝试通过HTTP来访问匹配"/secure/**"的网址,他们将首先被重定向到一个HTTPS的网址。可供选择的选项包括"http", "https"和"any"。使用"any"的属性值意味着通过HTTP或者HTTPS都可以访问。 如果你的应用程序没有使用默认的HTTP端口或HTTPS端口,你也可以下面的方式来映射一组端口: <http> ... <port-mappings> <port-mapping http="9080" https="9443"/> </port-mappings> </http>
|
你可以在第七章信道安全中,更深入地讨论信道的安全。 2.3.2.并发Session控制 如果你想在你的应用程序中限制单个用户的登录能力,Spring Security通过如下的步骤来支持这种方式。首先,你需要在web.xml文件中添加如下的listener,它可以使Spring Security更新session的生命周期事件: <listener> <listener-class>org.springframework.security.ui.session.HttpSessionEventPublisher </listener-class> </listener>
|
然后在应用上下文中添加以下行: <http> ... <concurrent-session-control max-sessions="1" /> </http>
|
这将防止用户在同一时间多次登录——第二次登录将导致第一次登录失效。如果你通常喜欢使用防止第二次登录的做法,在这种情况下,你可以使用: <http> ... <concurrent-session-control max-sessions="1" exception-if-maximum-exceeded="true"/> </http>
|
第二次登录将会被拒绝。 2.3.3. OpenID 登录 命名空间支持OpenID的方式登录,与普通的基于表单登录需要做如下的改变: <http auto-config='true'> <intercept-url pattern="/**" access="ROLE_USER" /> <openid-login /> </http>
|
你应该在OpenID提供商 (例如myopenid.com)注册一个账号,并在in-memory <user-service>中增加你的个人信息: <user name="http://jimi.hendrix.myopenid.com/" password="notused" authorities="ROLE_USER" />
|
你可以登录使用myopenid.com网站来验证。 2.3.4.添加你自己的过滤器 如果你曾经使用过Spring Security,你应该知道这个框架通过一组过滤器链来实现它的功能。你可能想要在配置文件中增加自己的过滤器,或者使用已经存在的定制版的过滤器。由于过滤器链没有直接暴露,你如何在命名空间中进行配置。 当使用命名空间时,过滤器始终按照严格的顺序进行配置。每个Spring Security过滤器都实现Spring中相应的接口,过滤器在初始化时就被排序好了。标准的过滤器在命名空间中都有别名: Table 2.1. 标准过滤器的别名和排序 Alias | Filter Class | CHANNEL_FILTER | ChannelProcessingFilter | CONCURRENT_SESSION_FILTER | ConcurrentSessionFilter | SESSION_CONTEXT_INTEGRATION_FILTER | HttpSessionContextIntegrationFilter | LOGOUT_FILTER | LogoutFilter | X509_FILTER | X509PreAuthenticatedProcessigFilter | PRE_AUTH_FILTER | Subclass of AstractPreAuthenticatedProcessingFilter | CAS_PROCESSING_FILTER | CasProcessingFilter | AUTHENTICATION_PROCESSING_FILTER | AuthenticationProcessingFilter | BASIC_PROCESSING_FILTER | BasicProcessingFilter | SERVLET_API_SUPPORT_FILTER | classname | REMEMBER_ME_FILTER | RememberMeProcessingFilter | ANONYMOUS_FILTER | AnonymousProcessingFilter | EXCEPTION_TRANSLATION_FILTER | ExceptionTranslationFilter | NTLM_FILTER | NtlmProcessingFilter | FILTER_SECURITY_INTERCEPTOR | FilterSecurityInterceptor | SWITCH_USER_FILTER | SwitchUserProcessingFilter |
你可以在配置文件中添加自己的过滤器,使用custom-filter元素并指定过滤器的位置: <beans:bean id="myFilter" class="com.mycompany.MySpecialAuthenticationFilter"> <custom-filter position="AUTHENTICATION_PROCESSING_FILTER"/> </beans:bean>
|
在配置文件中,如果你的过滤器将被配置在别的过滤器之前或者之后,你可以使用before或者after属性。"FIRST" 和 "LAST"名称代表你的过滤器将分别出现在整个配置文件的最前面和最后面。 2.3.5. Session Fixation攻击的保护 Session fixation攻击是一个潜在的危险,一个恶意的攻击者可能通过访问一个网站来创建一个Session,并劝说别的用户使用相同的Session来登录(例如,通过发送他们的链接包含session标识作为参数)。Spring Security通过每次自动创建一个新的Session,保护了用户自动登录的情况。如果你不需要这种保护,或者它与其他的需求相冲突,你可以通过<http>元素的session-fixation-protection属性来控制它,它包含3个选项: • migrateSession -
创建一个新的sesion,拷贝原来session的属性到新的session中。这是默认的情况。 • none - 不做任何事,原来的session将被保留。 • newSession - 创建一个新的“干净的”session,不拷贝已存在session的数据。 2.3.6. 设置一个自定义的AuthenticationEntryPoint 如果你在命名空间中不使用表单登陆、OpenID或者基本认证,你可能想使用传统的bean语法定义一个认证过滤器和切入点,并把他们配置在命名空间中。你可以使用2.3.4小节“添加你自己的过滤器”中的例子说明来添加过滤器。相应地,使用<http>元素的entry-point-ref属性可以设置AuthenticationEntryPoint。 CAS的事例是一个在配置文件中使用自定义的bean,并包含这些语法的好例子。 2.4. 方法安全 Spring Security 2.0已经对业务层方法的安全提供极大的支持。如果你使用Java5或者更高的版本,它提供支持JSR-250的安全注解,同时也提供框架的原生@secured注解。你可以在单个bean中应用安全框架,在bean中使用intercept-methods元素来声明它,你也可以使用AspectJ 风格的切点跨越整个业务层将安全框架应用到多个bean中。 2.4.1. <global-method-security> 元素 这个元素使得你的应用程序可以使用基于安全的注解(通过在元素中设置合适的属性),也可以在整个应用上下文中使用一组安全切点声明。你只能申明一个<global-method-security>元素。下面的声明将支持2种类型的注解: <global-method-security secured-annotations="enabled" jsr250-annotations="true"/>
|
2.4.1.1. 使用protect-pointcut来添加安全切点 protect-pointcut的功能是非常强大的,因为它允许你使用一段简单的声明在多个bean中使用安全框架。来看一下如下的例子: <global-method-security> <protect-pointcut expression="execution(* com.mycompany.*Service.*(..))" access="ROLE_USER"/> </global-method-security>
|
这将会保护所有在应用上下文中通过bean定义的,并且类在com.mycompany包下以及类的命名以"Service"结尾的业务方法。只有拥有ROLE_USER权限的用户才能调用这些方法。正如URL的匹配方式,最匹配的应该首先出现在切点列表中,因为将使用第一个匹配的表达式。 2.5. 默认的AccessDecisionManager 这一小节假设你理解Spring Security访问控制的底层架构的内容。如果你没有,你可以跳过它稍后再回来。因为这一小节的内容仅仅是提供给那些需要使用比基于角色安全机制更为复杂,并需要作一些自定义的人们。 当你使用命名空间的配置时,一个默认的AccessDecisionManager实例将被自动注册,它将为方法调用和URL进行访问控制,你可以在intercept-url和protect-pointcut元素中声明访问控制属性(如果你使用安全方法的注解,也可以在注解中声明访问控制)。 2.5.1. 自定义AccessDecisionManager 如果你需要使用一个比较复杂的访问控制策略,那么设置一个包括方法和web安全的备选是很简单的。 对于方法安全,你通过在global-security中设置access-decision-manager-ref属性来引用在应用上下文中适当的AccessDecisionManager的bean. <global-method-security access-decision-manager-ref="myAccessDecisionManagerBean"> ... </global-method-security>
|
Web安全的语法是相同的,但是在http元素中: <http access-decision-manager-ref="myAccessDecisionManagerBean"> ... </http>
|
2.5.2. 认证管理器 我们已经谈了在命名空间的配置中自动注册认证管理器bean的想法。这是一个Spring Security的ProviderManager类的实例,如果你曾经使用过框架那么你可能已经熟悉它了。 你可能想通过ProviderManager注册额外的AuthenticationProvider的bean,那么你可以在bean中使用<custom-authentication-provider>元素来实现它。例如: <bean id="casAuthenticationProvider" class="org.springframework.security.providers.cas.CasAuthenticationProvider"> <security:custom-authentication-provider /> ... </bean>
|
另一个常见的要求是,在应用上下文的另一个bean中可能需要引用AuthenticationManager。有一个特殊的元素可以让你为AuthenticationManager注册别名,然后你可以在应用上下文的其它地方使用这个别名。 <security:authentication-manager alias="authenticationManager"/> <bean id="casProcessingFilter" class="org.springframework.security.ui.cas.CasProcessingFilter"> <security:custom-filter position="CAS_PROCESSING_FILTER"/> <property name="authenticationManager" ref="authenticationManager"/> ... </bean>
|
|