命名空间配置从 Spring Framework 的 2.0 版本开始可用。它允许你用来自额外 XML 模式的元素来补充传统的 Spring bean 应用上下文语法。你可以在 Spring 参考文档找到更多的详细信息。命名空间元素可以简单地用于允许以更简洁的方式配置单个 bean,或者更有力地定义与问题域更紧密匹配并且向用户隐藏底层复杂性的替代配置语法。一个简单的元素可以隐藏被添加到应用程序上下文的多个 bean 和处理步骤。例如,从安全命名空间向应用程序上下文添加以下元素将启动嵌入式 LDAP 服务器,以便在应用程序中测试使用: <security:ldap-server />
这比连接等价的 Apache 目录服务器 bean 要简单得多。
要在应用程序上下文中开始使用安全名称空间,需要在类路径上设置 <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> 在本章中,我们将从现在开始使用这个语法。 命名空间被设计为捕获框架的最常见用途,并提供在应用程序中启用它们的简化和简洁语法。该设计基于框架内的大规模依赖关系,并且可以划分为以下区域:
我们将在下面的章节中介绍如何配置这些。 在本节中,我们将介绍如何构建名称空间配置以使用框架的一些主要特性。假设你最初希望尽快启动和运行,并添加身份验证支持和访问控制到现有的 web 应用程序,只需要一些测试登录。然后,我们将查看如何更改数据库或其它安全存储库的身份验证。在后面的部分中,我们将介绍更高级的命名空间配置选项。
你需要做的第一件事是将下面的过滤器声明添加到 <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> <intercept-url pattern="/**" access="hasRole('USER')" /> <form-login /> <logout /> </http>
也就是说,我们希望应用程序中的所有 URL 都受到保护,要求角色
若要添加一些用户,可以直接在命名空间中定义一组测试数据: <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>
这是一个安全的存储相同密码的例子。密码的前缀是 <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>
上面的配置定义了两个用户,它们的密码以及它们在应用程序中的角色(用于访问控制)。还可以使用 在这时,你应该能够启动你的应用程序,并且你将需要登录以继续进行。试一试,或者尝试使用项目附带的教程示例应用程序。 你可能想知道在提示你登录时,登录表单来自哪里,因为我们没有提到任何 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>
还请注意,我们添加了一个额外的 <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 中,现在可以使用多个
重要的是要认识到,这些不安全的请求将完全忽略任何与 Spring Security web 相关的配置或附加属性,例如 如果要使用基础身份验证而不是表单登录,则将配置更改为 <http use-expressions="false"> <intercept-url pattern="/**" access="ROLE_USER" /> <http-basic /> </http> 然后,基础身份验证将优先,在用户试图访问受保护的资源时,它将用于提示登录。如果你希望使用表单登录,例如通过嵌入在另一个网页中的登录表单,则该配置中仍然可以使用表单登录。
如果表单登录不是通过访问受保护资源来提示的,则 <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>
为了实现对目的地的更多控制,可以使用
在实践中,你需要比添加到应用程序上下文文件中的几个名称更可扩展的用户信息源。最有可能的是,你希望将用户信息存储在像数据库或 LDAP 服务器之类的东西中。LDAP 命名空间配置是在 LDAP 章节中处理的,所以我们不会在这里覆盖它。如果你有 Spring Security 的 <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" 是应用程序上下文中 <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>
还可以使用标准的 <authentication-manager> <authentication-provider ref='myAuthenticationProvider'/> </authentication-manager>
其中
密码应该始终使用为此目的设计的安全哈希算法(而不是像 SHA 或 MD5 这样的标准算法)进行编码。这是由 <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。 有关记住我命名空间配置的信息,请参阅单独的记住我章节。
如果你的应用程序同时支持 HTTP 和 HTTPS,并且你要求只能通过 HTTPS 访问特定的 URL,那么使用 <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)开始,并在整个过程中使用安全连接,以避免中间人攻击的任何可能性。
你可以配置 Spring Security,以检测提交无效的会话 ID 并将用户重定向到适当的 URL。这是通过 <http> ... <session-management invalid-session-url="/invalidSession.htm" /> </http> 注意,如果使用此机制检测会话超时,如果用户退出,然后在不关闭浏览器的情况下重新登录,则可能错误地报告错误。这是因为在使会话无效时没有清除会话 cookie,并且即使用户已经注销,也会重新提交会话 cookie。你可以在注销时显式地删除 JSESSIONID cookie,例如通过在注销处理程序中使用以下语法: <http> <logout delete-cookies="JSESSIONID" /> </http> 不幸的是,这不能保证与每个 servlet 容器一起工作,因此你需要在你的环境中测试它。
如果你希望限制单个用户登录到应用程序的能力,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>
第二次登录将被拒绝。拒绝,我们的意思是,如果使用基于表单的登录,用户将被发送到 如果使用自定义身份验证过滤器进行基于表单的登录,则必须显式配置并发会话控制支持。更多的详细信息可以在会话管理章节中找到。
会话固定攻击是一种潜在的风险,其中恶意攻击者可以通过访问站点来创建会话,然后说服另一个用户以相同的会话登录(例如,通过发送包含会话标识符的链接作为参数)。Spring Security 通过在用户登录时创建新会话或更改会话 ID 来自动防止这种情况。如果不需要这种保护,或者与其它要求冲突,可以使用
当发生会话固定保护时,它导致 命名空间支持 OpenID 登录,而不是基于正常形式的登录,除了一个简单的更改: <http> <intercept-url pattern="/**" access="ROLE_USER" /> <openid-login /> </http>
然后你应该向 OpenID 提供者(例如 myopenid.com)注册自己,并将用户信息添加到内存中的 <user name="http://jimi.hendrix.myopenid.com/" authorities="ROLE_USER" />
你应该能够使用 支持 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();
有关如何自定义 header 元素的其它信息,请参阅参考文档的小节 21, 安全的 HTTP 响应报头部分。
如果你以前使用过 Spring Security,你将知道框架维护了一系列过滤器以便应用其服务。你可能希望将自己的过滤器添加到特定位置的堆栈中,或者使用 Spring Security 过滤器,其中当前没有命名空间配置选项(例如,CAS)。或者你可能希望使用标准命名空间过滤器的定制版本,比如由 在使用命名空间时,过滤器的顺序总是严格执行的。在创建应用程序上下文时,过滤器 bean 根据命名空间处理代码进行排序,标准 Spring Security 过滤器在命名空间中都有别名和众所周知的位置。
创建过滤器的过滤器、别名和命名空间元素/属性如表格 6.1, “标准过滤器别名和排序”所示。过滤器按它们在过滤器链中出现的顺序列出。 Table 6.1. Standard Filter Aliases and Ordering
可以使用 <http> <custom-filter position="FORM_LOGIN_FILTER" ref="myFilter" /> </http> <beans:bean id="myFilter" class="com.mycompany.MySpecialAuthenticationFilter"/>
如果希望在堆栈中的另一个过滤器之前或之后插入过滤器,也可以使用
如果要替换需要身份验证入口点的命名空间过滤器(即,身份验证过程由未经身份验证的用户尝试访问安全资源触发),则还需要添加自定义入口点 bean。
如果你没有通过命名空间使用表单登录、OpenID 或基本身份验证,那么你可能希望使用传统的 bean 语法定义身份验证过滤器和入口点,并将它们链接到命名空间中,正如我们刚刚看到的。可以使用 CAS 示例应用程序是使用具有命名空间的自定义 bean 的一个很好的例子,包括这个语法。如果你不熟悉身份验证入口点,则在技术概览章节中讨论它们。
从版本 2.0 起,Spring Security 大大提高了对服务层方法安全的支持。它支持 JSR-250 注解安全以及框架的原始
这个元素用于在应用程序中启用基于注解的安全性(通过在元素上设置适当的属性),并且还用于将跨整个应用程序上下文应用的安全切入点声明分组在一起。仅声明一个 <global-method-security secured-annotations="enabled" />
将注解添加到方法(在类或接口上)将相应地限制对该方法的访问。Spring Security 的本地注解支持定义了该方法的一组属性。这些将传递给 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); } 如果需要定义超出根据用户权限列表检查角色名称的简单规则,那么基于表达式的注解是一个不错的选择。
<global-method-security> <protect-pointcut expression="execution(* com.mycompany.*Service.*(..))" access="ROLE_USER"/> </global-method-security>
这将保护在应用程序上下文中声明的 bean 上的所有方法,这些 bean 的类在 本节假定你对 Spring Security 中的访问控制的底层架构有一些了解。如果不能,可以跳过它,稍后再返回,因为本节只与需要执行一些定制以便使用不仅仅是简单的基于角色的安全的人有关。
当你使用命名空间的配置实例,
默认策略是使用 如果需要使用更复杂的访问控制策略,那么很容易为方法和 Web 安全设置备选方案。
对于方法安全,可以通过将 <global-method-security access-decision-manager-ref="myAccessDecisionManagerBean"> ... </global-method-security>
web 安全的语法是相同的,但是在 <http access-decision-manager-ref="myAccessDecisionManagerBean"> ... </http>
在 Spring Security 中提供身份验证服务的主要接口是
你可能希望向 <authentication-manager> <authentication-provider ref="casAuthenticationProvider"/> </authentication-manager> <bean id="casAuthenticationProvider" class="org.springframework.security.cas.authentication.CasAuthenticationProvider"> ... </bean>
另一个常见的要求是上下文中的另一个 bean 可能需要对 <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 身份验证 中了解更多关于 [2] 有关如何实际执行匹配的详细信息,请参阅 Web 应用程序基础结构章节中 小节 14.4, “请求匹配和 Http 防火墙” 的部分。 [3] 参阅 小节 23, 匿名身份验证 部分
[4] 例如,使用多个
[5] 有关如何实现通道处理的更多详细信息,请参阅 |