7. 安全命名空间配置

7.1 Introduction

从 Spring Framework 2.0 版开始,可以使用命名空间配置。它允许您使用其他 XML 模式中的元素来补充传统的 Spring bean 应用程序上下文语法。您可以在 Spring Reference Documentation中找到更多信息。名称空间元素可以简单地用于允许以更简洁的方式配置单个 bean,或者更强大地定义一种替代配置语法,该语法与问题域更紧密地匹配,并向用户隐藏底层的复杂性。一个简单的元素可能掩盖了将多个 bean 和处理步骤添加到应用程序上下文的事实。例如,将以下元素从安全名称空间添加到应用程序上下文中将启动嵌入式 LDAP 服务器,以测试应用程序中的使用情况:

<security:ldap-server />

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

要在应用程序上下文中开始使用安全性名称空间,您需要在 Classpath 上具有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>

在您将看到的许多示例中(以及在示例应用程序中),我们通常会使用“安全性”作为默认名称空间,而不是“ 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>

我们将从本章开始假设使用此语法。

7.1.1 命名空间的设计

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

    • Web/HTTP 安全性*-最复杂的部分。设置用于应用框架身份验证机制的过滤器和相关服务 Bean,以保护 URL,呈现登录页面和错误页面等。
  • *“业务对象(方法)安全性” *-用于保护服务层的选项。

    • AuthenticationManager *-处理来自框架其他部分的身份验证请求。
    • AccessDecisionManager *-提供有关 Web 和方法安全性的访问决策。默认的将被注册,但是您也可以选择使用自定义的,使用常规的 Spring bean 语法声明。
    • AuthenticationProvider * s-身份验证 Management 器用来验证用户身份的机制。命名空间提供了对几个标准选项的支持,还提供了添加使用传统语法声明的自定义 bean 的方法。
    • UserDetailsService *-与身份验证提供者密切相关,但其他 bean 通常也需要。

我们将在以下各节中了解如何配置它们。

7.2 安全命名空间配置入门

在本节中,我们将研究如何构建名称空间配置以使用框架的一些主要功能。假设您最初希望尽快启动并运行,并通过一些测试登录来向现有 Web 应用程序添加身份验证支持和访问控制。然后,我们将研究如何转换为针对数据库或其他安全存储库的身份验证。在后面的部分中,我们将介绍更高级的名称空间配置选项。

7.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>元素进行配置。

7.2.2 最低\ 配置

启用 Web 安全所需要做的就是

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

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

Note

===

您可以使用多个<intercept-url>元素来为不同的 URL 集定义不同的访问要求,但是将按照列出的 Sequences 对其进行评估,并且将使用第一个匹配项。因此,您必须将最具体的匹配项放在顶部。您还可以添加method属性,以将匹配限制为特定的 HTTP 方法(GETPOSTPUT等)。

===

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

<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,该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>

Note

如果您熟悉框架的命名空间前版本,则可能已经大概猜到这里发生了什么。 <http>元素负责创建FilterChainProxy及其使用的过滤器 bean。由于 sched 义了过滤器位置,不再像过滤器 Order 不正确这样的常见问题。

<authentication-provider>元素创建一个DaoAuthenticationProvider bean,而<user-service>元素创建一个InMemoryDaoImpl。所有authentication-provider元素都必须是<authentication-manager>元素的子元素,后者会创建ProviderManager并向其中注册身份验证提供程序。您可以找到有关在namespace appendix中创建的 bean 的更多详细信息。如果您想开始理解框架中的重要类是什么以及如何使用它们,则值得对此进行交叉检查,特别是如果您以后想要自定义内容的话。

上面的配置定义了两个用户,他们的密码和他们在应用程序中的角色(将用于访问控制)。也可以使用user-service上的properties属性从标准属性文件中加载用户信息。有关文件格式的更多详细信息,请参见in-memory authentication部分。使用<authentication-provider>元素意味着身份验证 Management 器将使用用户信息来处理身份验证请求。您可以有多个<authentication-provider>元素来定义不同的身份验证源,并且将依次查询每个。

此时,您应该能够启动应用程序,并且需要登录才能 continue。尝试一下,或尝试使用项目随附的“教程”示例应用程序。

7.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元素中省略了pattern属性,则它匹配所有请求。创建不安全的模式是此语法的一个简单示例,其中该模式被 Map 到一个空的过滤器链[4]。我们将在安全过滤链的一章中详细介绍这种新语法。

重要的是要意识到,这些不受保护的请求将完全忽略任何与 Spring Security Web 相关的配置或诸如requires-channel之类的其他属性,因此您将无法在请求期间访问当前用户的信息或调用受保护的方法。如果您仍然希望应用安全过滤器链,请使用access='IS_AUTHENTICATED_ANONYMOUSLY'作为替代。

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

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

然后,基本身份验证将具有优先权,并将在用户尝试访问受保护的资源时用于提示登录。如果您希望使用表单登录,则在此配置中仍然可用,例如通过嵌入在另一个网页中的登录表单。

设置默认的登录后目标

如果尝试访问受保护的资源未提示登录表单,则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的实例。您可以在Core Filters章节以及名称空间附录中找到有关此内容的更多信息,以及有关在身份验证失败时如何自定义流程的信息。

7.2.4 注销处理

logout元素通过导航到特定 URL 来增加对注销的支持。默认注销 URL 为/logout,但您可以使用logout-url属性将其设置为其他名称。有关其他可用属性的更多信息,请参见名称空间附录。

7.2.5 使用其他身份验证提供程序

实际上,与添加到应用程序上下文文件中的几个名称相比,您将需要更具扩展性的用户信息源。您很可能希望将您的用户信息存储在数据库或 LDAP 服务器之类的文件中。 LDAP 名称空间配置在LDAP chapter中处理,因此我们不在此介绍。如果您在应用程序上下文中具有 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 bean

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

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

添加密码编码器

密码应始终使用为此目的而设计的安全哈希算法(而不是诸如 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。

7.3 高级 Web 功能

7.3.1 记住我身份验证

有关“记住我”名称空间配置的信息,请参见单独的Remember-Me chapter

7.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 使用非标准端口,则可以指定端口 Map 列表,如下所示:

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

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

7.3.3 会话 Management

Detecting Timeouts

您可以配置 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

===如果您正在代理后运行应用程序,则还可以通过配置代理服务器来删除会话 cookie。例如,使用 Apache HTTPD 的 mod_headers,以下指令将通过在注销请求的响应中使JSESSIONID cookie 过期来删除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”)进行的,则“未授权”(401)错误将发送给 Client 端。相反,如果要使用错误页面,则可以将session-authentication-error-url属性添加到session-management元素。

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

会话固定攻击防护

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

  • 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,因此,如果您的代码侦听这两个事件,请务必谨慎。有关其他信息,请参见Session Management章。

7.3.4 OpenID 支持

通过简单的更改,命名空间支持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 使用。有关更多信息,请参见authentication providers的上一节。请注意,我们已经从上述用户配置中省略了 password 属性,因为这组用户数据仅用于加载用户的权限。系统会在内部生成一个随机密码,以防止您意外地将此用户数据用作配置中其他位置的身份验证源。

Attribute Exchange

支持 OpenID attribute exchange。例如,以下配置将尝试从 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 属性的“类型”是 URI,由特定模式确定,在这种情况下为http://axschema.org/。如果必须检索属性才能成功进行身份验证,则可以设置required属性。支持的确切架构和属性将取决于您的 OpenID 提供程序。属性值作为身份验证过程的一部分返回,之后可以使用以下代码进行访问:

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

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

7.3.5 响应标题

有关如何自定义 headers 元素的其他信息,请参见参考的第 10.8 节“安全 HTTP 响应头”部分。

7.3.6 添加自己的过滤器

如果您以前使用过 Spring Security,那么您会知道该框架会维护一系列过滤器以应用其服务。您可能想在特定位置将自己的过滤器添加到堆栈中,或使用当前没有名称空间配置选项(例如 CAS)的 Spring Security 过滤器。或者,您可能希望使用标准名称空间过滤器的自定义版本,例如<form-login>元素创建的UsernamePasswordAuthenticationFilter,以利用可通过显式使用 Bean 可用的一些额外配置选项。由于过滤器链没有直接公开,您如何使用名称空间配置来做到这一点?

使用名称空间时,始终严格执行过滤器的 Sequences。当创建应用程序上下文时,过滤器 bean 按名称空间处理代码排序,并且标准的 Spring Security 过滤器每个在名称空间中都有一个别名和一个众所周知的位置。

Note

===在以前的版本中,在应用程序上下文的后处理期间,排序是在创建过滤器实例之后进行的。在版本 3.0 中,现在在实例化类之前在 bean 元数据级别完成排序。这对您如何将自己的过滤器添加到堆栈有影响,因为在解析<http>元素时必须知道整个过滤器列表,因此语法在 3.0 中已稍有更改。 ===

表 7.1,“标准过滤器别名和排序”中显示了创建过滤器的过滤器,别名和名称空间元素/属性。筛选器按它们在筛选器链中出现的 Sequences 列出。

表 7.1. 标准过滤器别名和 Order

AliasFilter Class命名空间元素或属性
CHANNEL_FILTERChannelProcessingFilterhttp/[email protected]
SECURITY_CONTEXT_FILTERSecurityContextPersistenceFilterhttp
CONCURRENT_SESSION_FILTERConcurrentSessionFiltersession-management/concurrency-control
HEADERS_FILTERHeaderWriterFilterhttp/headers
CSRF_FILTERCsrfFilterhttp/csrf
LOGOUT_FILTERLogoutFilterhttp/logout
X509_FILTERX509AuthenticationFilterhttp/x509
PRE_AUTH_FILTERAbstractPreAuthenticatedProcessingFilter个子类N/A
CAS_FILTERCasAuthenticationFilterN/A
FORM_LOGIN_FILTERUsernamePasswordAuthenticationFilterhttp/form-login
BASIC_AUTH_FILTERBasicAuthenticationFilterhttp/http-basic
SERVLET_API_SUPPORT_FILTERSecurityContextHolderAwareRequestFilterhttp/@servlet-api-provision
JAAS_API_SUPPORT_FILTERJaasApiIntegrationFilterhttp/@jaas-api-provision
REMEMBER_ME_FILTERRememberMeAuthenticationFilterhttp/remember-me
ANONYMOUS_FILTERAnonymousAuthenticationFilterhttp/anonymous
SESSION_MANAGEMENT_FILTERSessionManagementFiltersession-management
EXCEPTION_TRANSLATION_FILTERExceptionTranslationFilterhttp
FILTER_SECURITY_INTERCEPTORFilterSecurityInterceptorhttp
SWITCH_USER_FILTERSwitchUserFilterN/A

您可以使用custom-filter元素和以下名称之一指定自己的过滤器到堆栈中,以指定过滤器应出现的位置:

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

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

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

Tip

===

如果您要插入一个自定义过滤器,该过滤器可能与名称空间创建的标准过滤器之一处于同一位置,那么不要误 Importing 名称空间版本,这一点很重要。删除所有创建要替换其功能的过滤器的元素。

请注意,您不能替换使用<http>元素本身创建的过滤器-SecurityContextPersistenceFilterExceptionTranslationFilterFilterSecurityInterceptor。默认情况下会添加其他一些过滤器,但是您可以禁用它们。默认情况下会添加AnonymousAuthenticationFilter,除非您禁用了session-fixation protection,否则还会将SessionManagementFilter添加到过滤器链中。

===

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

设置自定义 AuthenticationEntryPoint

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

CAS 示例应用程序是将自定义 bean 与命名空间结合使用的一个很好的例子,包括此语法。如果您不熟悉身份验证入口点,将在technical overview章中讨论它们。

7.4 方法安全性

从 2.0 版开始,Spring Security 大大改善了对为服务层方法增加安全性的支持。它提供对 JSR-250Comments 安全性以及框架原始@SecuredComments 的支持。从 3.0 开始,您还可以使用新的expression-based annotations。您可以使用intercept-methods元素修饰 bean 声明,从而对单个 bean 应用安全性,或者可以使用 AspectJ 样式切入点在整个服务层中保护多个 bean。

7.4.1<global-method-security>元素

此元素用于在应用程序中启用基于 Comments 的安全性(通过在元素上设置适当的属性),并将用于整个应用程序上下文的安全性切入点声明组合在一起。您只应声明一个<global-method-security>元素。以下声明将启用对 Spring Security 的@Secured的支持:

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

向方法(在类或接口上)添加 Comments 将相应地限制对该方法的访问。 Spring Security 的本机 Comments 支持为该方法定义了一组属性。这些将传递给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 的本机 Comments 的强大功能。要使用新的基于表达式的语法,您可以使用

<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);
}

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

Note

===带 Comments 的方法仅对于定义为 Spring bean 的实例(在启用方法安全性的同一应用程序上下文中)是安全的。如果要保护不是由 Spring 创建的实例(例如,使用new运算符),则需要使用 AspectJ。 ===

Note

===您可以在同一应用程序中启用不止一种类型的 Comments,但任何接口或类都只能使用一种类型的 Comments,否则将无法很好地定义行为。如果找到两个适用于特定方法的 Comments,则将仅应用其中一个。 ===

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

protect-pointcut的用法特别强大,因为它使您仅通过简单的声明就可以将安全性应用于许多 bean。考虑以下示例:

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

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

7.5 默认的 AccessDecisionManager

本部分假定您具有 Spring Security 中用于访问控制的基础架构的一些知识。如果不这样做,您可以跳过它,稍后再返回,因为此部分仅与需要进行一些自定义以便使用更多功能(而不是简单的基于角色的安全性)的人员有关。

当您使用名称空间配置时,默认的AccessDecisionManager实例会自动为您注册,并将根据您在intercept-urlprotect-pointcut声明(以及 Comments)中指定的访问属性,为方法调用和 Web URL 访问做出访问决策。 (如果您使用的是 Comments 安全方法)。

默认策略是将AffirmativeBased AccessDecisionManagerRoleVoterAuthenticatedVoter结合使用。您可以在authorization的章节中找到有关这些内容的更多信息。

7.5.1 自定义 AccessDecisionManager

如果您需要使用更复杂的访问控制策略,则可以轻松设置方法和 Web 安全性的替代方案。

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

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

网络安全的语法相同,但在http元素上:

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

7.6 身份验证 Management 器和命名空间

在 Spring Security 中提供身份验证服务的主要接口是AuthenticationManager。这通常是 Spring Security 的ProviderManager类的实例,如果您以前使用过该框架,则可能已经熟悉了。如果不是这样,稍后将在技术概述一章中进行介绍。使用authentication-manager名称空间元素注册 Bean 实例。如果通过命名空间使用 HTTP 或方法安全性,则不能使用自定义AuthenticationManager,但这应该不是问题,因为您可以完全控制所使用的AuthenticationProvider

您可能想要向ProviderManager注册其他AuthenticationProvider bean,并且可以使用具有ref属性的<authentication-provider>元素来执行此操作,其中该属性的值是要添加的提供程序 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]您可以在第 12.3 节“ LDAP 身份验证”的章节中找到有关ldap-server元素使用的更多信息。

[2]有关实际如何执行匹配的更多详细信息,请参见 Web 应用程序基础结构一章中第 10.1.4 节“请求匹配和 HttpFirewall”的部分。

[3]请参阅第 10.10 节“匿名身份验证”上的章节

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

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