在使用Spring Security的Web应用程序中,总会用到一些关键的过滤器,所以我们首先来看看这些过滤器及其支持类和接口。我们不会涵盖每一个特性,因此如果您想获得完整的图片,一定要查看JavaDoc中的特性。
10.2.1 FilterSecurityInterceptor
在讨论 access-control in general时,我们已经简要地看过了 FilterSecurityInterceptor ,并且我们已经将它与命名空间的<intercept url>元素组合在一起以进行内部配置一起使用。现在,我们将看到如何用 FilterChainProxy 来显式配置它,以及它的伴生过滤器 ExceptionTranslationFilter。典型配置示例如下所示:
<bean id="filterSecurityInterceptor"
class="org.springframework.security.web.access.intercept.FilterSecurityInterceptor">
<property name="authenticationManager" ref="authenticationManager"/>
<property name="accessDecisionManager" ref="accessDecisionManager"/>
<property name="securityMetadataSource">
<security:filter-security-metadata-source>
<security:intercept-url pattern="/secure/super/**" access="ROLE_WE_DONT_HAVE"/>
<security:intercept-url pattern="/secure/**" access="ROLE_SUPERVISOR,ROLE_TELLER"/>
</security:filter-security-metadata-source>
</property>
</bean>
FilterSecurityInterceptor 负责处理HTTP资源的安全性。它需要对 AuthenticationManager 和 AccessDecisionManager的引用。它还提供了适用于不同HTTP URL请求的配置属性。请参阅技术介绍 the original discussion on these 。
FilterSecurityInterceptor 可以通过两种方式携带属性参数进行配置。第一个是使用<filter-security-metadata-source>namespace元素,如上图所示。这类似于命名空间章节中的<http>元素,但<intercept url>子元素仅使用pattern 和 access 属性。逗号用于分隔应用于每个HTTP URL的不同配置属性。第二种选择是编写自己的SecurityMetadataSource,但这超出了本文档的范围。无论使用何种方法,SecurityMetadataSource 都负责返回一个包含与单个安全HTTP URL关联的所有配置属性的 List<ConfigAttribute>。
应该注意,FilterSecurityInterceptor.setSecurityMetadataSource() 方法实际需要FilterInvocationSecurityMetadataSource 的实例。这是SecurityMetadataSource类的一个子类。它只是表示SecurityMetadataSource 理解 FilterInvocations 。为了简单起见,我们将继续将FilterInvocationSecurityMetadataSource 称为SecurityMetadataSource,因为这种区别与大多数用户几乎没有关联。
由命名空间语法创建的 SecurityMetadataSource 通过将请求URL与已配置的 pattern 属性进行匹配来获取特定 FilterInvocation 的配置属性。这与命名空间配置的行为方式相同。默认情况下,将所有表达式视为Apache Ant路径,并用正则表达式来支持更复杂的情况。request-matcher 属性用于指定正在使用的模式类型。不能在同一定义中混合表达式语法。例如,以前使用正则表达式而不是Ant路径的配置将编写如下:
<bean id="filterInvocationInterceptor"
class="org.springframework.security.web.access.intercept.FilterSecurityInterceptor">
<property name="authenticationManager" ref="authenticationManager"/>
<property name="accessDecisionManager" ref="accessDecisionManager"/>
<property name="runAsManager" ref="runAsManager"/>
<property name="securityMetadataSource">
<security:filter-security-metadata-source request-matcher="regex">
<security:intercept-url pattern="\A/secure/super/.*\Z" access="ROLE_WE_DONT_HAVE"/>
<security:intercept-url pattern="\A/secure/.*\" access="ROLE_SUPERVISOR,ROLE_TELLER"/>
</security:filter-security-metadata-source>
</property>
</bean>
Patterns 总是按照其定义的顺序进行评估。因此,在列表中定义更具体的模式比定义不那么具体的模式要更靠前。这反映在上面的示例中,其中更具体的 /secure/super/ pattern比不那么具体的 /secure/ pattern 靠前。如果它们被颠倒,/secure/ pattern将始终匹配,而 /secure/super/ pattern将永远不会被评估。
10.2.2 异常处理过滤器 ExceptionTranslationFilter
ExceptionTranslationFilter 位于安全筛选器堆栈中 FilterSecurityInterceptor 的上方。它本身不执行任何实际的安全强制,但处理安全拦截器抛出的异常,并提供适当的HTTP响应。
<bean id="exceptionTranslationFilter"
class="org.springframework.security.web.access.ExceptionTranslationFilter">
<property name="authenticationEntryPoint" ref="authenticationEntryPoint"/>
<property name="accessDeniedHandler" ref="accessDeniedHandler"/>
</bean>
<bean id="authenticationEntryPoint"
class="org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint">
<property name="loginFormUrl" value="/login.jsp"/>
</bean>
<bean id="accessDeniedHandler"
class="org.springframework.security.web.access.AccessDeniedHandlerImpl">
<property name="errorPage" value="/accessDenied.htm"/>
</bean>
身份验证入口点 AuthenticationEntryPoint
如果用户请求受保护的HTTP资源,但未对其进行身份验证,则将调用 AuthenticationEntryPoint 。安全拦截器将沿着调用堆栈进一步抛出适当的 AuthenticationException 或 AccessDeniedException ,从而在入口点触发 commence 方法。这样做的目的是向用户提供适当的响应,以便可以开始身份验证。我们在这里使用的是LoginLauthenAuthenticationEntryPoint,它将请求重定向到不同的URL(通常是登录页面)。实际使用的AuthenticationEntryPoint 实现类取决于您希望在应用程序中使用的身份验证机制。
访问拒绝控制器 AccessDeniedHandler
如果用户已经过身份验证通过,并且试图访问受保护的资源,会发生什么情况呢?在正常使用中,不应该发生这种情况,因为应用程序工作流应限制为用户可以访问的操作。例如,指向管理页面的HTML链接可能对没有管理角色的用户隐藏。但是,您不能依靠隐藏链接来实现安全性,因为用户总是有可能直接输入URL来绕过这些限制。或者他们可以修改一个RESTful URL来更改一些参数值。您的应用程序必须针对这些场景进行保护,否则它肯定是不安全的。通常,您将使用简单的Web层安全性来对基本URL应用约束,并在服务层接口上使用更具体的基于方法的安全性来真正确定允许的内容。
如果抛出了 AccessDeniedException ,并且用户已经通过了身份验证,那么这意味着这个用户尝试了一个没有足够权限的操作。在这种情况下,ExceptionTranslationFilter 将调用第二个策略,即 AccessDeniedHandler。默认情况下,使用 AccessDeniedHandlerImpl ,它只向客户机发送403(禁止)响应。或者,您可以显式配置一个实例(如上面的示例所示),并设置一个可以被请求转发到的错误页URL。这可以是一个简单的“拒绝访问”页面,如JSP,也可以是一个更复杂的处理程序,如MVC控制器。当然,您可以自己实现接口并使用自己的实现。
还可以在使用命名空间配置应用程序时提供自定义的 AccessDeniedHandler 。有关详细信息,请参阅 the namespace appendix。
SavedRequests 和 RequestCache接口
ExceptionTranslationFilter 的另一个职责是在调用 AuthenticationEntryPoint 之前保存当前请求。这允许在用户进行身份验证后还原请求(请参阅先前的Web身份验证概述)。一个典型的例子是,用户使用一个表单登录,然后由默认的SavedRequestAwareAuthenticationSuccessHandler (见下文)重定向到原始URL。
RequestCache 封装了存储和检索 HttpServletRequest 实例所需的功能。默认情况下,使用HttpSessionRequestCache ,它将请求存储在 HttpSession 中。当用户被重定向到原始URL时,RequestCacheFilter的任务是从缓存中实际还原保存的请求。
在正常情况下,您不需要修改任何此功能,但保存的请求处理是一种“尽最大努力”的方法,并且可能存在默认配置无法处理的情况。这些接口的使用使得它可以从SpringSecurity3.0开始完全插入。
10.2.3 安全上下文持久性筛选器 SecurityContextPersistenceFilter
我们在技术概述一章中介绍了这个非常重要的过滤器的用途,因此您可能希望在此时重新阅读该部分。让我们先看看如何配置它与 FilterChainProxy 一起使用。基本配置只需要bean本身。
<bean id="securityContextPersistenceFilter"
class="org.springframework.security.web.context.SecurityContextPersistenceFilter"/>
正如我们之前看到的,这个过滤器有两个主要任务。它负责在HTTP请求之间存储SecurityContext内容,并在请求完成时清除 SecurityContextHolder。清除存储上下文的 ThreadLocal 是非常重要的,因为线程可能会被替换到servlet容器的线程池中,而特定用户的安全上下文仍然附加。该线程随后可能会在后期使用,使用错误的凭据执行操作。
安全上下文存储库 SecurityContextRepository
从SpringSecurity3.0开始,加载和存储安全上下文的工作现在被委托给一个单独的策略接口:
public interface SecurityContextRepository {
SecurityContext loadContext(HttpRequestResponseHolder requestResponseHolder);
void saveContext(SecurityContext context, HttpServletRequest request,
HttpServletResponse response);
}
HttpRequestResponseHolder 只是接收请求和响应对象的容器,允许用包装类的实现来替换这些对象。返回的内容将传递到过滤器链。
默认实现是 HttpSessionSecurityContextRepository,它将安全上下文存储为 HttpSession 属性。此实现最重要的配置参数是 allowSessionCreation 属性,该属性默认为 true,从而允许类在需要会话时为经过身份验证的用户存储安全上下文时创建 session(除非进行了身份验证,并且安全上下文的内容发生了改变,否则不会创建)。如果不希望创建session ,则可以将此属性设置为 false:
<bean id="securityContextPersistenceFilter"
class="org.springframework.security.web.context.SecurityContextPersistenceFilter">
<property name='securityContextRepository'>
<bean class='org.springframework.security.web.context.HttpSessionSecurityContextRepository'>
<property name='allowSessionCreation' value='false' />
</bean>
</property>
</bean>
或者,你可以提供一个 NullSecurityContextRepository 实例,一个空对象实现,它将阻止存储安全上下文,即使在请求期间已经创建了会话。
10.2.4 UsernamePasswordAuthenticationFilter
我们现在看到了三个主要的过滤器,它们总是出现在Spring Security Web配置中。这三个元素也是由namespace <http> 元素自动创建的,不能用替代项替换。现在唯一缺少的是一个实际的身份验证机制,它允许用户进行身份验证。此筛选器是最常用的身份验证筛选器,也是最常用的自定义筛选器。它还提供由命名空间中的<form login>元素使用的实现。配置它需要三个阶段。
像上面所做的那样,使用登录页面的URL配置一个LoginLauthenAuthenticationEntryPoint,并将其设置在ExceptionTranslationFilter上。
实现登录页面(使用JSP或MVC控制器)。
在应用程序上下文中配置 UsernamePasswordAuthenticationFilter 的实例
将过滤器bean添加到您的过滤器链代理(确保您关注顺序)。
登录表单只包含用户名和密码输入字段,并发布到由过滤器监控的URL(默认为/login)。基本的过滤器配置如下所示:
<bean id="authenticationFilter" class=
"org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter">
<property name="authenticationManager" ref="authenticationManager"/>
</bean>
Application Flow on Authentication Success and Failure
过滤器调用配置的 AuthenticationManager 来处理每个身份验证请求。成功身份验证或身份验证失败后的目标分别由AuthenticationSuccessHandler 和 AuthenticationFailureHandler 策略接口控制。过滤器具有允许您设置属性,因此您可以完全自定义行为。提供了一些标准实现,如 SimpleUrlAuthenticationSuccessHandler、SavedRequestAwareAuthenticationSuccessHandler、SimpleUrlAuthenticationFailureHandler、ExceptionMappingAuthenticationFailureHandler 和 DelegatingAuthenticationFailureHandler。多了解这些类的javadoc,以及 AbstractAuthenticationProcessingFilter ,了解它们的工作原理和支持的功能。
如果身份验证成功,则生成的 Authentication 将放入SecurityContextHolder。然后,将调用配置的AuthenticationSuccessHandler ,将用户重定向或转发到适当的目标。默认情况下,会使用SavedRequestAwareAuthenticationSuccessHandler ,这意味着用户将被重定向到请求登录之前所请求的原始目标。
ExceptionTranslationFilter 缓存用户发出的原始请求。当用户进行身份验证时,请求处理程序使用此缓存请求获取原始URL并重定向到该URL。然后重新构建原始请求并将其用作替代请求。
如果身份验证失败,将调用配置的 AuthenticationFailureHandler 。