安全命名空间配置
命名空间配置自Spring框架2.0版本起就已可用。它允许你在传统的Spring beans应用程序上下文语法中添加来自附加XML模式的元素。你可以在Spring的参考文档中找到更多信息。你可以使用命名空间元素来更简洁地配置单个bean,或者更强大地定义一种替代配置语法,使其更紧密地匹配问题领域,并向用户隐藏底层复杂性。一个简单的元素可以掩盖正在向应用程序上下文中添加多个bean和处理步骤的事实。例如,将以下来自security
命名空间的元素添加到应用程序上下文中,会启动一个嵌入式的LDAP服务器,供应用程序内部测试使用:
<security:ldap-server/>
<security:ldap-server />
这比连接等效的Apache Directory Server bean要简单得多。ldap-server
元素的属性支持最常见的替代配置需求,并且用户不必担心需要创建哪些bean以及bean属性名称是什么。你可以在LDAP 身份验证章节中了解有关ldap-server
元素使用的更多信息。在编辑应用程序上下文文件时,一个好的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
,这是由命名空间创建的用于处理 web 安全的内部基础结构 bean。请注意,你不应该自己使用这个 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 中的访问控制不仅限于使用简单的角色(因此使用前缀来区分不同类型的 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>
元素来定义不同的认证来源。每个来源会依次被咨询。
此时,你应该能够启动你的应用程序,并且需要登录才能继续。尝试一下,或者试着使用项目自带的“tutorial”示例应用程序进行实验。
设置默认登录后目标
如果表单登录不是由尝试访问受保护资源触发的,则 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)。或者,你可能希望使用标准命名空间过滤器的自定义版本,例如由 <form-login>
元素创建的 UsernamePasswordAuthenticationFilter
,以便利用显式使用 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版本开始,你还可以使用基于表达式的注解。你可以通过使用 intercept-methods
元素来装饰 bean 声明,从而将安全性应用于单个 bean,或者你可以使用 AspectJ 风格的切入点在整个服务层中保护多个 bean。
默认的 AccessDecisionManager
本节假设你对 Spring Security 中访问控制的基础架构有一定了解。如果你没有相关知识,可以跳过这部分,稍后再回来看,因为本节仅适用于需要进行一些自定义以使用不仅仅是基于角色的简单安全机制的人。
当你使用命名空间配置时,会自动为你注册一个默认的 AccessDecisionManager
实例,并根据你在 intercept-url
和 protect-pointcut
声明中指定的访问属性(以及如果你使用注解来保护方法的话,在注解中指定的访问属性),用于对方法调用和 Web URL 访问进行访问决策。
默认策略是使用带有 RoleVoter
和 AuthenticatedVoter
的 AffirmativeBased
AccessDecisionManager
。你可以在关于授权的章节中了解更多相关信息。
自定义 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>