6. Security 命名空间配置

6.1 介绍

命名空间配置从 Spring Framework 的 2.0 版本开始可用。它允许你用来自额外 XML 模式的元素来补充传统的 Spring bean 应用上下文语法。你可以在 Spring 参考文档找到更多的详细信息。命名空间元素可以简单地用于允许以更简洁的方式配置单个 bean,或者更有力地定义与问题域更紧密匹配并且向用户隐藏底层复杂性的替代配置语法。一个简单的元素可以隐藏被添加到应用程序上下文的多个 bean 和处理步骤。例如,从安全命名空间向应用程序上下文添加以下元素将启动嵌入式 LDAP 服务器,以便在应用程序中测试使用:

<security:ldap-server />

这比连接等价的 Apache 目录服务器 bean 要简单得多。ldap-server 元素上的属性支持最常见的替代配置需求,并且用户不用担心需要创建哪些 bean 以及 bean 属性名是什么。[1]。在编辑应用程序上下文文件时使用良好的 XML 编辑器应该提供有关可用的属性和元素的信息。我们建议你尝试 Spring 工具套件,因为它具有使用标准 Spring 命名空间的特殊特性。

要在应用程序上下文中开始使用安全名称空间,需要在类路径上设置 spring-security-config jar。然后,你需要做的就是将架构声明添加到应用程序上下文文件中:

<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:security="http://www.springframework.org/schema/security"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
		http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
		http://www.springframework.org/schema/security
		http://www.springframework.org/schema/security/spring-security.xsd">
	...
</beans>

在你将看到的许多示例中(以及在示例应用程序中),我们经常将 "security" 用作默认名称空间,而不是 "beans",这意味着我们可以省略所有安全名称空间元素上的前缀,使内容更容易阅读。如果你的应用程序上下文被划分为单独的文件,并且大部分安全配置都位于其中之一,那么你可能希望还这样做。然后,你的安全应用程序上下文文件将这样启动

<beans:beans xmlns="http://www.springframework.org/schema/security"
xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
		http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
		http://www.springframework.org/schema/security
		http://www.springframework.org/schema/security/spring-security.xsd">
	...
</beans:beans>

在本章中,我们将从现在开始使用这个语法。

6.1.1 命名空间的设计

命名空间被设计为捕获框架的最常见用途,并提供在应用程序中启用它们的简化和简洁语法。该设计基于框架内的大规模依赖关系,并且可以划分为以下区域:

  • Web/HTTP Security - 最复杂的部分。设置用于应用框架身份验证机制、保护 URL、呈现登录和错误页面等的过滤器和相关服务 bean。
  • Business Object (Method) Security - 用于保护服务层的选项。
  • AuthenticationManager - 处理来自框架的其它部分的身份验证请求。
  • AccessDecisionManager - 为 web 和方法安全提供访问决策。默认的一个将被注册,但你也可以选择使用自定义的,使用普通的 Spring bean 语法声明。
  • AuthenticationProvider - 认证管理器对用户进行身份验证的机制。命名空间提供了对几个标准选项的支持,以及添加使用传统语法声明的自定义 bean 的方法。
  • UserDetailsService - 与身份验证提供者密切相关,但通常也需要其它 bean。

我们将在下面的章节中介绍如何配置这些。

6.2 Security 命名空间配置新手入门

在本节中,我们将介绍如何构建名称空间配置以使用框架的一些主要特性。假设你最初希望尽快启动和运行,并添加身份验证支持和访问控制到现有的 web 应用程序,只需要一些测试登录。然后,我们将查看如何更改数据库或其它安全存储库的身份验证。在后面的部分中,我们将介绍更高级的命名空间配置选项。

6.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 基础结构提供了一个钩子。DelegatingFilterProxy 是一个 Spring Framework 类,它委托给在应用程序上下文中定义为 Spring bean 的过滤器实现。在这种情况下,bean 被命名为 "springSecurityFilterChain",它是由名称空间创建的内部基础设施 bean,用于处理 web 安全。注意,你不应该自己使用这个 bean 名称。一旦将此添加到 web.xml 中,就可以开始编辑应用程序上下文文件。Web 安全服务是使用 <http> 元素配置的。

6.2.2 最少化 <http> 配置

开始启用 web 安全所需的全部是

<http>
<intercept-url pattern="/**" access="hasRole('USER')" />
<form-login />
<logout />
</http>

也就是说,我们希望应用程序中的所有 URL 都受到保护,要求角色 ROLE_USER 来访问它们,我们希望使用具有用户名和密码的表单登录到应用程序,并且我们希望注册一个注销 URL,该 URL 允许我们注销应用程序。<http> 元素是所有与 web 相关的命名空间功能的父级。<intercept-url> 元素使用 ant 路径样式语法定义与传入请求的 URL 匹配的模式 [2]。你还可以使用正则表达式匹配作为替代(参见命名空间附录以获得更多的详细信息)。access 属性定义与给定模式匹配的请求的访问要求。使用默认配置,这通常是一个逗号分隔的角色列表,其中必须允许用户发出请求。前缀 "ROLE_" ”是一个标记,表示应该与用户权限进行简单的比较。换言之,应该使用正常的基于角色的检查。Spring Security 中的访问控制不限于使用简单的角色(因此使用前缀来区分不同类型的安全属性)。稍后我们将看到解释如何改变脚注:[access 属性中逗号分隔值的解释取决于所使用的 –1— 的实现。在 Spring Security 3.0 中,属性也可以用 –2— 填充。

[Note] Note

你可以使用多个 <intercept-url> 元素来为不同的 URL 集定义不同的访问需求,但是将按照列出的顺序评估它们,并使用第一个匹配。所以你必须把最特定的匹配放在最上面。还可以添加 method 属性来将匹配限制为特定的 HTTP 方法(GET、POST、PUT 等)。

若要添加一些用户,可以直接在命名空间中定义一组测试数据:

<authentication-manager>
<authentication-provider>
	<user-service>
	<!-- Password is prefixed with {noop} to indicate to DelegatingPasswordEncoder that
	NoOpPasswordEncoder should be used. This is not safe for production, but makes reading
	in samples easier. Normally passwords should be hashed using BCrypt -->
	<user name="jimi" password="{noop}jimispassword" authorities="ROLE_USER, ROLE_ADMIN" />
	<user name="bob" password="{noop}bobspassword" authorities="ROLE_USER" />
	</user-service>
</authentication-provider>
</authentication-manager>

这是一个安全的存储相同密码的例子。密码的前缀是 {bcrypt},以指示 DelegatingPasswordEncoder(支持任何配置的 PasswordEncoder 进行匹配)使用 BCrypt 散列密码:

<authentication-manager>
<authentication-provider>
	<user-service>
	<user name="jimi" password="{bcrypt}$2a$10$ddEWZUl8aU0GdZPPpy7wbu82dvEw/pBpbRvDQRqA41y6mK1CoH00m"
			authorities="ROLE_USER, ROLE_ADMIN" />
	<user name="bob" password="{bcrypt}$2a$10$/elFpMBnAYYig6KRR5bvOOYeZr1ie1hSogJryg9qDlhza4oCw1Qka"
			authorities="ROLE_USER" />
	<user name="jimi" password="{noop}jimispassword" authorities="ROLE_USER, ROLE_ADMIN" />
	<user name="bob" password="{noop}bobspassword" authorities="ROLE_USER" />
	</user-service>
</authentication-provider>
</authentication-manager>

如果你熟悉框架的预命名空间版本,你可能已经大致猜到了这里发生了什么。<http> 元素负责创建它使用的 FilterChainProxy 和 过滤器 bean。当过滤器位置被预定义时,常见的问题,如不正确的过滤器排序不再是问题。

<authentication-provider> 元素创建一个 DaoAuthenticationProvider bean,而 <user-service> 元素创建了一个 InMemoryDaoImpl。所有 authentication-provider 元素都必须是 <authentication-manager> 元素的子元素,它创建了一个 ProviderManager 并用它注册身份验证提供者。你可以在命名空间附录中找到有关 bean 的更详细的信息。如果你想开始理解框架中的重要类是什么以及它们是如何被使用的,特别是如果你想在以后定制东西的话,这是值得一试的。

上面的配置定义了两个用户,它们的密码以及它们在应用程序中的角色(用于访问控制)。还可以使用 user-service 上的 properties 属性从标准属性文件加载用户信息。有关文件格式的更多详细信息,请参阅内存中的身份验证的章节。使用 <authentication-provider> 元素意味着身份验证管理器将使用用户信息来处理身份验证请求。你可以具有多个 <authentication-provider> 元素来定义不同的身份验证源,每个元素将依次进行咨询。

在这时,你应该能够启动你的应用程序,并且你将需要登录以继续进行。试一试,或者尝试使用项目附带的教程示例应用程序。

6.2.3 表单和基础登录选项

你可能想知道在提示你登录时,登录表单来自哪里,因为我们没有提到任何 HTML 文件或 JSP。事实上,由于我们没有显式地为登录页面设置 URL,Spring Security 根据启用的特性和为处理提交的登录的 URL 使用标准值,自动生成一个 URL,用户在登录后将发送到默认目标 URL,依此类推在。但是,命名空间提供了大量支持,允许你自定义这些选项。例如,如果你想提供自己的登录页面,可以使用:

<http>
<intercept-url pattern="/login.jsp*" access="IS_AUTHENTICATED_ANONYMOUSLY"/>
<intercept-url pattern="/**" access="ROLE_USER" />
<form-login login-page='/login.jsp'/>
</http>

还请注意,我们添加了一个额外的 intercept-url 元素,用于说明对登录页面的任何请求都应该对匿名用户可用 [3],并且还添加了 AuthenticatedVoter 类,以获取关于如何处理值 IS_AUTHENTICATED_ANONYMOUSLY 的更多细节。否则,请求将被模式 /** 匹配,并且不可能访问登录页本身!这是一个常见的配置错误,将导致应用程序中的无限循环。如果你的登录页面看起来被保护,Spring Security 将在日志中发出警告。通过为模式定义一个单独的 http 元素,还可以使与特定模式匹配的所有请求完全绕过安全过滤器链,如下所示:

<http pattern="/css/**" security="none"/>
<http pattern="/login.jsp*" security="none"/>

<http use-expressions="false">
<intercept-url pattern="/**" access="ROLE_USER" />
<form-login login-page='/login.jsp'/>
</http>

在 Spring Security 3.1 中,现在可以使用多个 http 元素来为不同的请求模式定义单独的安全过滤器链配置。如果从 http 元素中省略了模式属性,则匹配所有请求。创建一个非安全模式是这种语法的一个简单示例,其中模式映射到一个空的过滤器链 [4]。我们将在安全过滤器链的章节中更详细地了解这个新的语法。

重要的是要认识到,这些不安全的请求将完全忽略任何与 Spring Security web 相关的配置或附加属性,例如 requires-channel,因此在请求期间你将无法访问关于当前用户的信息或调用安全方法。如果你仍然希望使用安全过滤器链,请使用 access='IS_AUTHENTICATED_ANONYMOUSLY' 作为替代方案。

如果要使用基础身份验证而不是表单登录,则将配置更改为

<http use-expressions="false">
<intercept-url pattern="/**" access="ROLE_USER" />
<http-basic />
</http>

然后,基础身份验证将优先,在用户试图访问受保护的资源时,它将用于提示登录。如果你希望使用表单登录,例如通过嵌入在另一个网页中的登录表单,则该配置中仍然可以使用表单登录。

设置默认的 POST 登录目的地

如果表单登录不是通过访问受保护资源来提示的,则 default-target-url 选项开始发挥作用。这是用户成功登录后将要访问的 URL,默认为 "/"。你还可以通过将 always-use-default-target 属性设置为 "true" 来配置内容,以便用户总是以该页面结束(无论登录是按需还是显式选择登录)。如果你的应用程序总是要求用户在 "home" 页启动,这是有用的,例如:

<http pattern="/login.htm*" security="none"/>
<http use-expressions="false">
<intercept-url pattern='/**' access='ROLE_USER' />
<form-login login-page='/login.htm' default-target-url='/home.htm'
		always-use-default-target='true' />
</http>

为了实现对目的地的更多控制,可以使用 authentication-success-handler-ref 属性作为 default-target-url 的替代。引用的 bean 应该是 AuthenticationSuccessHandler 的实例。你将在核心过滤器章节以及命名空间附录中找到关于这个的更多详细信息,以及关于在身份验证失败时如何自定义流的信息。

6.2.4 注销处理

logout 元素通过导航到特定 URL 添加对日志记录的支持。默认注销 URL 是 /logout,但可以使用 logout-url 属性将其设置为其它内容。关于其它可用属性的更多信息可以在命名空间附录中找到。

6.2.5 使用其它身份验证提供者

在实践中,你需要比添加到应用程序上下文文件中的几个名称更可扩展的用户信息源。最有可能的是,你希望将用户信息存储在像数据库或 LDAP 服务器之类的东西中。LDAP 命名空间配置是在 LDAP 章节中处理的,所以我们不会在这里覆盖它。如果你有 Spring Security 的 UserDetailsService 的自定义实现,在你的应用程序上下文中称为 "myUserDetailsService",那么你可以使用

<authentication-manager>
	<authentication-provider user-service-ref='myUserDetailsService'/>
</authentication-manager>

如果你想使用一个数据库,那么你可以使用

<authentication-manager>
<authentication-provider>
	<jdbc-user-service data-source-ref="securityDataSource"/>
</authentication-provider>
</authentication-manager>

其中 "securityDataSource" 是应用程序上下文中 DataSource bean 的名称,指向包含标准 Spring Security 用户数据表的数据库。或者,你可以配置 Spring Security JdbcDaoImpl bean,并使用 user-service-ref 属性指向它:

<authentication-manager>
<authentication-provider user-service-ref='myUserDetailsService'/>
</authentication-manager>

<beans:bean id="myUserDetailsService"
	class="org.springframework.security.core.userdetails.jdbc.JdbcDaoImpl">
<beans:property name="dataSource" ref="dataSource"/>
</beans:bean>

还可以使用标准的 AuthenticationProvider beans,如下所示

<authentication-manager>
	<authentication-provider ref='myAuthenticationProvider'/>
</authentication-manager>

其中 myAuthenticationProvider 是应用程序上下文中实现 AuthenticationProvider 的 bean 的名称。可以使用多个 authentication-provider 元素,在这种情况下,将按照声明提供者的顺序查询提供者。有关如何使用命名空间配置 Spring Security AuthenticationManager 的更多详细信息,请参阅 小节 6.6, “身份验证管理器和命名空间”。

添加密码编码器

密码应该始终使用为此目的设计的安全哈希算法(而不是像 SHA 或 MD5 这样的标准算法)进行编码。这是由 <password-encoder> 元素支持的。使用 bcrypt 编码的密码,原来的认证提供者配置将是这样的:

<beans:bean name="bcryptEncoder"
	class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder"/>

<authentication-manager>
<authentication-provider>
	<password-encoder ref="bcryptEncoder"/>
	<user-service>
	<user name="jimi" password="$2a$10$ddEWZUl8aU0GdZPPpy7wbu82dvEw/pBpbRvDQRqA41y6mK1CoH00m"
			authorities="ROLE_USER, ROLE_ADMIN" />
	<user name="bob" password="$2a$10$/elFpMBnAYYig6KRR5bvOOYeZr1ie1hSogJryg9qDlhza4oCw1Qka"
			authorities="ROLE_USER" />
	</user-service>
</authentication-provider>
</authentication-manager>

对于大多数情况,bcrypt 是一个很好的选择,除非你有一个遗留系统强迫你使用不同的算法。如果你使用的是简单的哈希算法,或者更糟糕的是,存储纯文本密码,那么你应该考虑迁移到更安全的选项,如 bcrypt。

6.3 高级 Web 特性

6.3.1 记住我身份验证

有关记住我命名空间配置的信息,请参阅单独的记住我章节。

6.3.2 添加 HTTP/HTTPS 通道安全

如果你的应用程序同时支持 HTTP 和 HTTPS,并且你要求只能通过 HTTPS 访问特定的 URL,那么使用 <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 URL [5]。可用的选项是 "http"、"https" 或 "any"。使用 "any" 值意味着可以使用 HTTP 或 HTTPS。

如果应用程序使用非标准端口进行 HTTP 和/或 HTTPS,则可以指定端口映射列表如下:

<http>
...
<port-mappings>
	<port-mapping http="9080" https="9443"/>
</port-mappings>
</http>

请注意,为了真正安全,应用程序根本不应该使用 HTTP 或者在 HTTP 和 HTTPS 之间切换。它应该从 HTTPS(用户输入 HTTPS URL)开始,并在整个过程中使用安全连接,以避免中间人攻击的任何可能性。

6.3.3 会话管理

检测超时

你可以配置 Spring Security,以检测提交无效的会话 ID 并将用户重定向到适当的 URL。这是通过 session-management 元素实现的:

<http>
...
<session-management invalid-session-url="/invalidSession.htm" />
</http>

注意,如果使用此机制检测会话超时,如果用户退出,然后在不关闭浏览器的情况下重新登录,则可能错误地报告错误。这是因为在使会话无效时没有清除会话 cookie,并且即使用户已经注销,也会重新提交会话 cookie。你可以在注销时显式地删除 JSESSIONID cookie,例如通过在注销处理程序中使用以下语法:

<http>
<logout delete-cookies="JSESSIONID" />
</http>

不幸的是,这不能保证与每个 servlet 容器一起工作,因此你需要在你的环境中测试它。

[Note] Note

如果你在代理后面运行应用程序,你也可以通过配置代理服务器来删除会话 cookie。例如,使用 Apache HTTPD 的 mod_headers,以下指令将通过在响应注销请求时过期来删除 JSESSIONID cookie(假设应用程序部署在路径 /tutorial 下):

<LocationMatch "/tutorial/logout">
Header always set Set-Cookie "JSESSIONID=;Path=/tutorial;Expires=Thu, 01 Jan 1970 00:00:00 GMT"
</LocationMatch>

并发会话控制

如果你希望限制单个用户登录到应用程序的能力,Spring Security 通过以下简单添加支持开箱即用。首先,需要将以下监听器添加到 web.xml 文件中,以确保 Spring Security 对会话生命周期事件的更新:

<listener>
<listener-class>
	org.springframework.security.web.session.HttpSessionEventPublisher
</listener-class>
</listener>

然后将以下行添加到应用程序上下文中:

<http>
...
<session-management>
	<concurrency-control max-sessions="1" />
</session-management>
</http>

这将防止用户多次登录 - 第二次登录将导致第一个无效。通常,你希望避免第二次登录,在这种情况下,你可以使用

<http>
...
<session-management>
	<concurrency-control max-sessions="1" error-if-maximum-exceeded="true" />
</session-management>
</http>

第二次登录将被拒绝。拒绝,我们的意思是,如果使用基于表单的登录,用户将被发送到 authentication-failure-url。如果第二次身份验证通过另一个非交互机制进行,如 "remember-me",则会将 "unauthorized" (401) 错误发送到客户端。如果希望使用错误页,则可以向 session-management 元素添加属性 session-authentication-error-url。

如果使用自定义身份验证过滤器进行基于表单的登录,则必须显式配置并发会话控制支持。更多的详细信息可以在会话管理章节中找到。

会话固定攻击保护

会话固定攻击是一种潜在的风险,其中恶意攻击者可以通过访问站点来创建会话,然后说服另一个用户以相同的会话登录(例如,通过发送包含会话标识符的链接作为参数)。Spring Security 通过在用户登录时创建新会话或更改会话 ID 来自动防止这种情况。如果不需要这种保护,或者与其它要求冲突,可以使用 <session-management> 上的 session-fixation-protection 属性控制行为,该属性具有四个选项

  • none - 什么也不要做。原会话将保留。
  • newSession - 创建一个新的干净会话,而不复制现有会话数据(Spring Security 相关属性仍将被复制)。
  • migrateSession - 创建新会话并将所有现有会话属性复制到新会话。这是 Servlet 3.0 或旧容器中的默认值。
  • changeSessionId - 不创建新的会话。相反,使用 Servlet 容器提供的会话固定保护。(HttpServletRequest#changeSessionId())。此选项仅在 Servlet 3.1(Java EE 7)和更新的容器中可用。在旧容器中指定它将导致异常。这是 servlet 3.1 和更新容器中的默认值。

当发生会话固定保护时,它导致 SessionFixationProtectionEvent 在应用程序上下文中发布。如果使用 changeSessionId,此保护还将导致通知任何 javax.servlet.http.HttpSessionIdListener,因此如果代码监听两个事件,请谨慎。有关的额外信息,请参阅会话管理章节。

6.3.4 OpenID 支持

命名空间支持 OpenID 登录,而不是基于正常形式的登录,除了一个简单的更改:

<http>
<intercept-url pattern="/**" access="ROLE_USER" />
<openid-login />
</http>

然后你应该向 OpenID 提供者(例如 myopenid.com)注册自己,并将用户信息添加到内存中的 <user-service>:

<user name="http://jimi.hendrix.myopenid.com/" authorities="ROLE_USER" />

你应该能够使用 myopenid.com 站点进行身份验证。还可以通过在 openid-login 元素上设置 user-service-ref 属性来选择特定的 UserDetailsService bean 以使用 OpenID。有关的更多详细信息,请参阅上一节 身份验证提供者。注意,我们在上面的用户配置中省略了密码属性,因为这组用户数据仅用于为用户加载权限。在内部将生成一个随机密码,防止你在配置的其它地方意外地使用此用户数据作为身份验证源。

属性交换

支持 OpenID 属性交换。作为示例,以下配置将尝试从 OpenID 提供者检索电子邮件和全名,以供应用程序使用:

<openid-login>
<attribute-exchange>
	<openid-attribute name="email" type="http://axschema.org/contact/email" required="true"/>
	<openid-attribute name="name" type="http://axschema.org/namePerson"/>
</attribute-exchange>
</openid-login>

每个 OpenID 属性的 "type" 是由特定模式确定的 URI,在这种情况下是 http://axschema.org/。如果必须检索属性以进行成功的身份验证,则可以设置所需的属性。所支持的确切模式和属性将取决于 OpenID 提供者。属性值作为身份验证过程的一部分返回,并且可以使用以下代码访问该属性值:

OpenIDAuthenticationToken token =
	(OpenIDAuthenticationToken)SecurityContextHolder.getContext().getAuthentication();
List<OpenIDAttribute> attributes = token.getAttributes();

OpenIDAttribute 包含属性类型和检索值(或多值属性的情况下的值)。我们将在技术概览章节中查看核心 Spring Security 组件时更多地了解如何使用 SecurityContextHolder 类。如果你希望使用多个身份提供者,也支持多属性交换配置。可以使用每个 identifier-matcher 属性提供多个 attribute-exchange 元素。这包含与用户提供的 OpenID 标识符匹配的正则表达式。参见代码库中的 OpenID 示例应用程序示例配置,为 Google、Yahoo 和 MyOpenID 提供者提供不同的属性列表。

6.3.5 响应报头

有关如何自定义 header 元素的其它信息,请参阅参考文档的小节 21, 安全的 HTTP 响应报头部分。

6.3.6 添加你自己的过滤器

如果你以前使用过 Spring Security,你将知道框架维护了一系列过滤器以便应用其服务。你可能希望将自己的过滤器添加到特定位置的堆栈中,或者使用 Spring Security 过滤器,其中当前没有命名空间配置选项(例如,CAS)。或者你可能希望使用标准命名空间过滤器的定制版本,比如由 <form-login> 元素创建的 UsernamePasswordAuthenticationFilter,它利用了显式使用 bean 的一些额外配置选项。由于过滤器链不直接暴露,你如何使用命名空间配置来实现这一点?

在使用命名空间时,过滤器的顺序总是严格执行的。在创建应用程序上下文时,过滤器 bean 根据命名空间处理代码进行排序,标准 Spring Security 过滤器在命名空间中都有别名和众所周知的位置。

[Note] Note

在以前的版本中,排序是在创建过滤器实例之后在应用程序上下文的后处理期间进行的。在版本 3.0+ 中,排序是在 bean 实例化级别之前完成的,在实例化类之前。这暗示了如何向堆栈中添加自己的过滤器,因为在解析 <http> 元素期间必须知道整个过滤器列表,所以语法在 3.0 中略有改变。

创建过滤器的过滤器、别名和命名空间元素/属性如表格 6.1, “标准过滤器别名和排序”所示。过滤器按它们在过滤器链中出现的顺序列出。

Table 6.1. Standard Filter Aliases and Ordering

别名 过滤类 命名空间元素和属性

CHANNEL_FILTER

ChannelProcessingFilter

http/intercept-url@requires-channel

SECURITY_CONTEXT_FILTER

SecurityContextPersistenceFilter

http

CONCURRENT_SESSION_FILTER

ConcurrentSessionFilter

session-management/concurrency-control

HEADERS_FILTER

HeaderWriterFilter

http/headers

CSRF_FILTER

CsrfFilter

http/csrf

LOGOUT_FILTER

LogoutFilter

http/logout

X509_FILTER

X509AuthenticationFilter

http/x509

PRE_AUTH_FILTER

AbstractPreAuthenticatedProcessingFilterSubclasses

N/A

CAS_FILTER

CasAuthenticationFilter

N/A

FORM_LOGIN_FILTER

UsernamePasswordAuthenticationFilter

http/form-login

BASIC_AUTH_FILTER

BasicAuthenticationFilter

http/http-basic

SERVLET_API_SUPPORT_FILTER

SecurityContextHolderAwareRequestFilter

http/@servlet-api-provision

JAAS_API_SUPPORT_FILTER

JaasApiIntegrationFilter

http/@jaas-api-provision

REMEMBER_ME_FILTER

RememberMeAuthenticationFilter

http/remember-me

ANONYMOUS_FILTER

AnonymousAuthenticationFilter

http/anonymous

SESSION_MANAGEMENT_FILTER

SessionManagementFilter

session-management

EXCEPTION_TRANSLATION_FILTER

ExceptionTranslationFilter

http

FILTER_SECURITY_INTERCEPTOR

FilterSecurityInterceptor

http

SWITCH_USER_FILTER

SwitchUserFilter

N/A


可以使用 custom-filter 元素和这些名称之一来指定筛选器应该出现在:

<http>
<custom-filter position="FORM_LOGIN_FILTER" ref="myFilter" />
</http>

<beans:bean id="myFilter" class="com.mycompany.MySpecialAuthenticationFilter"/>

如果希望在堆栈中的另一个过滤器之前或之后插入过滤器,也可以使用 after 或 before 属性。名称 "FIRST" 和 "LAST" 可以与 position 属性一起使用,以指示希望过滤器分别出现在整个堆栈之前或之后。

[Tip] Avoiding filter position conflicts

如果插入自定义过滤器,该过滤器可能与由命名空间创建的标准过滤器占用的位置相同,那么重要的是不要错误地包含命名空间版本。删除任何要替换的功能创建过滤器的元素。

注意,不能替换通过使用 <http> 元素本身创建的过滤器 - SecurityContextPersistenceFilter、ExceptionTranslationFilter 或 FilterSecurityInterceptor。默认情况下添加了一些其它的过滤器,但你可以禁用它们。默认情况下添加 AnonymousAuthenticationFilter,除非禁用了会话固定保护,否则还将向过滤器链添加 SessionManagementFilter。

如果要替换需要身份验证入口点的命名空间过滤器(即,身份验证过程由未经身份验证的用户尝试访问安全资源触发),则还需要添加自定义入口点 bean。

设置一个自定义身份验证入口点

如果你没有通过命名空间使用表单登录、OpenID 或基本身份验证,那么你可能希望使用传统的 bean 语法定义身份验证过滤器和入口点,并将它们链接到命名空间中,正如我们刚刚看到的。可以使用 <http> 元素上的 entry-point-ref 属性来设置相应的 AuthenticationEntryPoint

CAS 示例应用程序是使用具有命名空间的自定义 bean 的一个很好的例子,包括这个语法。如果你不熟悉身份验证入口点,则在技术概览章节中讨论它们。

6.4 方法安全

从版本 2.0 起,Spring Security 大大提高了对服务层方法安全的支持。它支持 JSR-250 注解安全以及框架的原始 @Secured 注解。从 3.0 开始,你还可以使用新的基于表达式的注解。你可以使用 intercept-methods 元素对单个 bean 应用安全性,以修饰 bean 声明,或者你可以使用 AspectJ 样式切入点在整个服务层上保护多个 bean。

6.4.1 <global-method-security> 元素

这个元素用于在应用程序中启用基于注解的安全性(通过在元素上设置适当的属性),并且还用于将跨整个应用程序上下文应用的安全切入点声明分组在一起。仅声明一个 <global-method-security> 元素。下面的声明将支持 Spring Security 的 @Secured:

<global-method-security secured-annotations="enabled" />

将注解添加到方法(在类或接口上)将相应地限制对该方法的访问。Spring Security 的本地注解支持定义了该方法的一组属性。这些将传递给 AccessDecisionManager,以便它做出实际决策:

public interface BankService {

@Secured("IS_AUTHENTICATED_ANONYMOUSLY")
public Account readAccount(Long id);

@Secured("IS_AUTHENTICATED_ANONYMOUSLY")
public Account[] findAccounts();

@Secured("ROLE_TELLER")
public Account post(Account account, double amount);
}

JSR-250 注解可以启用使用

<global-method-security jsr250-annotations="enabled" />

这些是基于标准的,并且允许应用简单的基于角色的约束,但是没有强大的 Spring Security 的本地注解。若要使用基于表达式的新语法,将使用

<global-method-security pre-post-annotations="enabled" />

等效的 Java 代码将是

public interface BankService {

@PreAuthorize("isAnonymous()")
public Account readAccount(Long id);

@PreAuthorize("isAnonymous()")
public Account[] findAccounts();

@PreAuthorize("hasAuthority('ROLE_TELLER')")
public Account post(Account account, double amount);
}

如果需要定义超出根据用户权限列表检查角色名称的简单规则,那么基于表达式的注解是一个不错的选择。

[Note] Note

带注解的方法将只对定义为 Spring bean 的实例(在启用方法安全性的应用程序上下文中)进行保护。如果希望保护 Spring 没有创建的实例(例如,使用 new 操作符),则需要使用 AspectJ。

[Note] Note

你可以在同一个应用程序中启用多于一种类型的注解,但是对于任何接口或类,都应该只使用一种类型,否则行为将不被很好地定义。如果找到两个适用于特定方法的注解,那么将只应用其中一个。

使用保护切入点添加安全切入点

protect-pointcut 的作用特别强大,因为它允许你仅使用一个简单的声明就可以对许多 bean 应用安全性。参考下面的例子:

<global-method-security>
<protect-pointcut expression="execution(* com.mycompany.*Service.*(..))"
	access="ROLE_USER"/>
</global-method-security>

这将保护在应用程序上下文中声明的 bean 上的所有方法,这些 bean 的类在 com.mycompany 包中,类名以 "Service" 结尾。只有具有 ROLE_USER 角色的用户才能够调用这些方法。与 URL 匹配一样,最特定的匹配必须首先出现在切入点列表中,因为将使用第一个匹配表达式。安全注解优先于切入点。

6.5 默认的访问判断管理器

本节假定你对 Spring Security 中的访问控制的底层架构有一些了解。如果不能,可以跳过它,稍后再返回,因为本节只与需要执行一些定制以便使用不仅仅是简单的基于角色的安全的人有关。

当你使用命名空间的配置实例,AccessDecisionManager 的默认实例是自动为你注册的,并将用于为方法调用和 web URL 访问的访问判断,它是基于 intercept-url 和 protect-pointcut 声明(以及注解中,如果你使用注解安全方法)中指定的访问属性。

默认策略是使用 AffirmativeBased AccessDecisionManager 与 RoleVoter 和 AuthenticatedVoter。你可以在身份验证章节找到更多的详细信息。

6.5.1 自定义访问判断管理器

如果需要使用更复杂的访问控制策略,那么很容易为方法和 Web 安全设置备选方案。

对于方法安全,可以通过将 global-method-security 的 access-decision-manager-ref 属性设置为应用程序上下文中适当的 AccessDecisionManager bean 的 id 来实现这一点:

<global-method-security access-decision-manager-ref="myAccessDecisionManagerBean">
...
</global-method-security>

web 安全的语法是相同的,但是在 http 元素上:

<http access-decision-manager-ref="myAccessDecisionManagerBean">
...
</http>

6.6 身份验证管理器和命名空间

在 Spring Security 中提供身份验证服务的主要接口是 AuthenticationManager。这通常是 Spring Security 的 ProviderManager 类的一个实例,如果你以前使用过这个框架,你可能已经熟悉这个类了。如果不是,它将在后面的技术概览章节中被覆盖。bean 实例使用 authentication-manager 元素注册。如果通过命名空间使用 HTTP 或方法安全性,则不能使用自定义身份验证管理器,但这不应该是问题,因为你完全控制了所使用的 AuthenticationProvider。

你可能希望向 ProviderManager 注册其它 AuthenticationProvider bean,并且可以使用 <authentication-provider> 元素和 ref 属性进行注册,其中属性的值是你想要添加的提供者 bean 的名称。例如:

<authentication-manager>
<authentication-provider ref="casAuthenticationProvider"/>
</authentication-manager>

<bean id="casAuthenticationProvider"
	class="org.springframework.security.cas.authentication.CasAuthenticationProvider">
...
</bean>

另一个常见的要求是上下文中的另一个 bean 可能需要对 AuthenticationManager 的引用。你可以轻松地为 AuthenticationManager 注册别名,并在应用程序上下文中别处使用此名称。

<security:authentication-manager alias="authenticationManager">
...
</security:authentication-manager>

<bean id="customizedFormLoginFilter"
	class="com.somecompany.security.web.CustomFormLoginFilter">
<property name="authenticationManager" ref="authenticationManager"/>
...
</bean>


[1] 你可以在 小节 30, LDAP 身份验证 中了解更多关于 ldap-server 元素的使用。

[2] 有关如何实际执行匹配的详细信息,请参阅 Web 应用程序基础结构章节中 小节 14.4, “请求匹配和 Http 防火墙” 的部分。

[3] 参阅 小节 23, 匿名身份验证 部分

[4] 例如,使用多个 <http> 元素是一个重要特性,允许命名空间同时支持同一应用程序中的有状态和无状态路径。前面的语法,在 intercept-url 元素上使用属性 filters="none" 与此更改不兼容,并且在 3.1 中不再支持。

[5] 有关如何实现通道处理的更多详细信息,请参阅 ChannelProcessingFilter 和相关类的 JavaDoc。