安全命名空间配置
自Spring Framework 2.0版本起,命名空间配置功能已可用。它允许您通过额外的XML模式元素来补充传统的Spring beans应用上下文语法。您可以在Spring参考文档中找到更多信息。您可以使用命名空间元素来更简洁地配置单个bean,或者更强大的是,定义一种更贴近问题领域、并向用户隐藏底层复杂性的替代配置语法。一个简单的元素可以隐藏向应用上下文添加多个bean和处理步骤的事实。例如,将以下来自security命名空间的元素添加到应用上下文,会启动一个嵌入式LDAP服务器供应用程序内部测试使用:
<security:ldap-server />
这比配置等效的UnboundID服务器Bean要简单得多。ldap-server元素支持的属性涵盖了最常见的替代配置需求,用户无需关心需要创建哪些Bean以及Bean属性的名称。关于ldap-server元素的使用,您可以在LDAP认证章节中找到更多信息。在编辑应用程序上下文文件时,一个好的XML编辑器应该能提供可用属性和元素的信息。我们建议您尝试使用Spring Tool Suite,因为它具有处理标准Spring命名空间的特殊功能。
要在你的应用上下文中开始使用 security 命名空间,请将 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
https://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/security
https://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
https://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/security
https://www.springframework.org/schema/security/spring-security.xsd">
...
</beans:beans>
我们假设从现在开始本章将使用这种语法。
命名空间的设计
该命名空间旨在捕获框架中最常见的用途,并提供简化且简洁的语法,以便在应用程序中启用它们。其设计基于框架内的大规模依赖关系,可分为以下几个领域:
-
Web/HTTP 安全 是最复杂的部分。它负责设置过滤器和相关的服务 Bean,用于应用框架的身份验证机制、保护 URL、渲染登录和错误页面等。
-
业务对象(方法)安全 定义了保护服务层的选项。
-
AuthenticationManager 处理来自框架其他部分的身份验证请求。
-
AccessDecisionManager 为 Web 和方法安全提供访问决策。框架会注册一个默认的,但您也可以选择使用自定义的,通过标准的 Spring Bean 语法声明即可。
-
AuthenticationProvider 实例提供了身份验证管理器对用户进行身份验证的机制。命名空间支持几种标准选项,并提供了添加使用传统语法声明的自定义 Bean 的方法。
-
UserDetailsService 与身份验证提供者密切相关,但也经常被其他 Bean 所要求。
我们将在以下章节中学习如何配置这些内容。
安全命名空间配置入门
本节将介绍如何构建命名空间配置,以使用该框架的一些主要功能。我们假设您最初希望尽快启动并运行,为现有的 Web 应用程序添加身份验证支持和访问控制,并提供一些测试登录。然后,我们将探讨如何切换到针对数据库或其他安全存储库进行身份验证。在后续章节中,我们将介绍更高级的命名空间配置选项。
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>
DelegatingFilterProxy 是 Spring 框架中的一个类,它负责将请求委托给在应用程序上下文中定义为 Spring bean 的过滤器实现。在本例中,该 bean 名为 springSecurityFilterChain,这是一个由命名空间创建的内部基础设施 bean,用于处理 Web 安全。请注意,您不应自行使用此 bean 名称。一旦将此 bean 添加到您的 web.xml 中,您就可以开始编辑应用程序上下文文件。Web 安全服务通过 <http> 元素进行配置。
一个最小的 <http> 配置
要启用 Web 安全,你需要以下配置:
<http>
<intercept-url pattern="/**" access="hasRole('USER')" />
<form-login />
<logout />
</http>
该清单表明我们需要:
-
应用程序内的所有URL均需受保护,要求具备
ROLE_USER角色方可访问 -
通过包含用户名和密码的表单登录应用程序
-
注册一个注销URL,允许用户退出应用程序
<http> 元素是所有与 Web 相关命名空间功能的父元素。<intercept-url> 元素定义了一个 pattern,该模式使用 Ant 路径语法与传入请求的 URL 进行匹配。关于匹配实际如何执行的更多细节,请参阅 HttpFirewall 部分。你也可以使用正则表达式匹配作为替代方案(更多细节请参阅命名空间附录)。access 属性定义了与给定模式匹配的请求的访问要求。在默认配置中,这通常是一个以逗号分隔的角色列表,用户必须拥有其中至少一个角色才被允许发出请求。ROLE_ 前缀是一个标记,表示应与用户的权限进行简单比较。换句话说,应使用基于角色的常规检查。Spring Security 中的访问控制不限于使用简单角色(因此使用前缀来区分不同类型的安全属性)。我们稍后会看到其解释如何变化。access 属性中逗号分隔值的解释取决于所使用的 AccessDecisionManager 的实现。从 Spring Security 3.0 开始,你还可以使用 EL 表达式 来填充该属性。
你可以使用多个 <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>
上述配置定义了两个用户、他们的密码以及在应用程序中的角色(用于访问控制)。您还可以通过设置 user-service 元素的 properties 属性,从标准属性文件加载用户信息。有关文件格式的更多详细信息,请参阅内存认证部分。使用 <authentication-provider> 元素意味着认证管理器将使用用户信息来处理认证请求。您可以配置多个 <authentication-provider> 元素来定义不同的认证源,系统将依次查询每个认证源。
此时,您应该能够启动应用程序,并且需要登录才能继续操作。请尝试启动,或体验项目中自带的“教程”示例应用程序。
设置默认登录后跳转目标
如果访问受保护资源时未触发表单登录提示,那么 default-target-url 选项就会发挥作用。这是用户成功登录后跳转的 URL,默认值为 /。您还可以通过将 always-use-default-target 属性设置为 true 来配置系统,使用户始终跳转至此页面(无论登录是“按需触发”还是用户主动选择登录)。例如,当您的应用程序始终要求用户从“首页”开始时,这个功能就非常有用:
<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 的实例。
高级 Web 功能
本节将介绍一些超越基础知识的进阶功能。
添加自定义过滤器
如果你曾使用过 Spring Security,就会知道该框架维护着一个过滤器链,用于应用其服务。你可能希望在特定位置向过滤器栈中添加自己的过滤器,或者使用 Spring Security 中目前没有命名空间配置选项的过滤器(例如 CAS)。或者,你可能希望使用标准命名空间过滤器的自定义版本,比如 UsernamePasswordAuthenticationFilter(由 <form-login> 元素创建),以便利用显式使用 bean 时可用的额外配置选项。既然过滤器链没有直接暴露,你该如何通过命名空间配置来实现这一点呢?
使用命名空间时,过滤器的顺序始终被严格遵循。当应用上下文被创建时,过滤器bean会由命名空间处理代码进行排序,并且每个标准的Spring Security过滤器在命名空间中都有一个别名和一个已知的位置。
在之前的版本中,排序是在过滤器实例创建之后,在应用上下文的后处理阶段进行的。在 3.0+ 版本中,排序现在是在 bean 元数据级别完成的,在类被实例化之前。这对你如何将自己的过滤器添加到过滤器栈中产生了影响,因为在解析 <http> 元素期间,必须知道整个过滤器列表,因此在 3.0 版本中,语法略有变化。
创建过滤器的过滤器、别名和命名空间元素及属性如下表所示,按它们在过滤器链中出现的顺序排列:
表 1. 标准过滤器别名与排序
| 别名 | 过滤器类 | 命名空间元素或属性 |
|---|---|---|
| DISABLE_ENCODE_URL_FILTER | DisableEncodeUrlFilter | http@disable-url-rewriting |
| FORCE_EAGER_SESSION_FILTER | ForceEagerSessionCreationFilter | http@create-session="ALWAYS" |
| 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 | AbstractPreAuthenticatedProcessingFilter 子类 | 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 属性结合使用,分别表示您希望您的过滤器出现在整个堆栈之前或之后。
避免过滤器位置冲突
如果你插入的自定义过滤器可能与命名空间创建的标准过滤器占据相同位置,应注意不要错误地包含命名空间版本。移除那些创建了你想要替换功能的过滤器的元素。
请注意,你无法替换由 <http> 元素本身创建的过滤器:SecurityContextPersistenceFilter、ExceptionTranslationFilter 或 FilterSecurityInterceptor。默认情况下,会添加一个 AnonymousAuthenticationFilter,并且除非你禁用了会话固定保护,否则还会向过滤器链中添加一个 SessionManagementFilter。
如果你替换了一个需要身份验证入口点的命名空间过滤器(即,身份验证过程由未经身份验证的用户尝试访问受保护资源而触发),那么你也需要添加一个自定义的入口点 Bean。
方法安全
自2.0版本起,Spring Security 为服务层方法添加安全性提供了实质性支持。它不仅支持 JSR-250 注解安全,也支持框架原生的 @Secured 注解。从3.0版本开始,您还可以使用基于表达式的注解。您可以将安全性应用于单个 Bean(通过使用 intercept-methods 元素来装饰 Bean 声明),也可以使用 AspectJ 风格的切点来保护整个服务层的多个 Bean。
默认的 AccessDecisionManager
本节假设您对 Spring Security 中访问控制的基础架构有一定了解。如果您不了解,可以跳过本节稍后再回来看,因为本节仅适用于那些需要进行一些定制以使用超出简单基于角色的安全机制的人员。
当您使用命名空间配置时,系统会自动为您注册一个默认的 AccessDecisionManager 实例。该实例会根据您在 intercept-url 和 protect-pointcut 声明中指定的访问属性(以及通过注解保护方法时的注解属性),对方法调用和 Web URL 访问进行访问决策。
默认策略是使用一个基于AffirmativeBased的AccessDecisionManager,并配备一个RoleVoter和一个AuthenticatedVoter。你可以在授权章节中找到更多关于这些内容的信息。
自定义 AccessDecisionManager
如果你需要使用更复杂的访问控制策略,可以为方法和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>