Chapter 8. 核心安全过滤器

这儿有几个在web应用中一直会用到的Spring Security关键过滤器, 所以我们回来看看它们,和支持的类和接口。 我们不会覆盖所有功能,所以确认参考它们的javadoc,如果你想要获得完全的信息。

8.1. FilterSecurityInterceptor

我们已经简要了解了FilterSecurityInterceptor,在简要讨论访问控制的时候 (见#tech-intro-access-control),我们也已经在命名空间中使用过它, <intercept-url>元素的结合在内部对它进行了配置。 现在我们会看如何精确的配置它,使用FilterChainProxy, 通过结合ExceptionTranslationFilter过滤器。 一个典型的配置例子如下:

<bean id="filterSecurityInterceptor"
        class="org.springframework.security.intercept.web.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资源的安全。 它需要一个AuthenticationManagerAccessDecisionManager的引用。 它也需要不同HTTP URL请求的配置属性。 引用回#tech-intro-config-attributes这里可以看到原始信息。

FilterSecurityInterceptor可是通过两种方式定义配置属性。 第一种,向上面演示的,使用<filter-security-metadata-source> 命名空间元素。这和用来配置FilterChainProxy<filter-chain-map>一样,但是使用的是 <intercept-url>子元素,只使用patternaccess属性。逗号用来分隔不同的配置属性,对于每个HTTP URL。 第二个选择是编写你自己的SecurityMetadataSource, 但是这超越了我们的文档的范围。根据使用的方式, SecurityMetadataSource负责返回一个包含了所有配置属性 的List<ConfigAttribute>,它分配给一个单独的安全HTTP URL。

应该注意的是FilterSecurityInterceptor.setSecurityMetadataSource()方法 其实需要一个FilterSecurityMetadataSource实例。 这是一个标记接口,它是 SecurityMetadataSource的子类。 它只标记了SecurityMetadataSource需要 FilterInvocation。对于相似感兴趣,我们会继续引用 FilterInvocationDefinitionSource作为一个 SecurityMetadataSource,作为区别大多数用户的微笑相关不同。

SecurityMetadataSource通过命名空间获得配置属性, 为一个特定的 FilterInvocation,通过匹配请求URL,对于 配置好的pattern属性。这个行为和它在命名空间中配置的一样。 默认使用apache ant path的方式来处理所有表达式,也支持正则表达式来支持更复杂的请求。 这个path-type属性用来指定模式使用的类型。 它不可能在同一个定义中使用多个表达式语法的复合结构。作为一个例子,上面的配置使用正则表达式 替换了ant path,可以写成下面这样:

<bean id="filterInvocationInterceptor"
     class="org.springframework.security.intercept.web.FilterSecurityInterceptor">
  <property name="authenticationManager" ref="authenticationManager"/>
  <property name="accessDecisionManager" ref="accessDecisionManager"/>
  <property name="runAsManager" ref="runAsManager"/>
  <property name="securityMetadataSource">
    <security:filter-security-metadata-source path-type="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>        

模式总是根据他们定义的顺序进行执行。因此很重要的是,把更确定的模式定义到列表的上面。 这会反映在你上面的例子中,更确定的/secure/super/模式放在,没那么确定的 /secure/模式的上面。如果它们被反转了。/secure/会一直 被匹配,/secure/super/就永远也不会执行。

8.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>

8.2.1. AuthenticationEntryPoint

AuthenticationEntryPoint会被调用, 在用户请求一个安全HTTP资源,但是他们还没有被认证。一个对应的 AuthenticationExceptionAccessDeniedException会被抛出, 由一个安全监听器在下面的调用栈中,触发入口点的 commence方法。这执行展示对应的响应给用户, 这样认证可以开始。我们这里使用的是 LoginUrlAuthenticationEntryPoint,它会把请求 重定向到另一个不同的URL(一般是一个登陆页面)。实际的使用使用会依赖 你希望使用到的认证机制。

8.2.2. AccessDeniedHandler

如果一个用户已经认证了,他再去访问一个被保护的资源时会怎么样呢? 正常情况下,这不会发生,因为应用工作流应该限制哪些资源用户可以访问。 比如一个HTML连接到管理员页面,可能对没有管理权限的用户隐藏。 你不能对一个隐藏的链接点击,因为安全的原因,这总有可能用户直接输入了URL尝试越过限制。 或者他们可能修改了RESTful URL来改变一些参数的值。你的应用必须保护这些场景, 或者它们会变的不安全。你将使用简单的web层安全在你的服务层接口上, 来确认哪些是被允许的。

如果一个AccessDeniedException被抛出了, 一个用户已经被认证,然后这意味着操作已经被尝试了,而他们没有足够的权限, 这种情况下,ExceptionTranslationFilter会调用第二个策略, AccessDeniedHandler。默认下,一个 AccessDeniedHandlerImpl会被使用,它只发送一个403(拒绝访问) 响应到客户端。可选的,你可以确切配置一个实例(像上面的例子)然后设置一个错误页URL 它会把请求跳转到错误页。 [6]。 这可能是一个简单的“access denied”页面,比如一个JSP,或者它可能是一个 更加复杂的处理器,比如一个MVC控制器。当然,我们可以实现自己的接口, 使用你自己的实现。

也可能提供一个自定义的AccessDeniedHandler 然后使用命名空间配置你的应用,参考#nsa-access-denied-handler.

8.3. SecurityContextPersistenceFilter

我们介绍了所有重要的过滤器,在技术概述一章, 所以你可能希望重新阅读以下这些章节。让我们首先看一下如何使用 FilterChainProxy配置它们。 一个基本的配置需要bean自己

<bean id="securityContextPersistenceFilter"
    class="org.springframework.security.web.context.SecurityContextPersistenceFilter"/>
    

像我们之前看到的,这个过滤器有两个主要的任务,它负责保存在不同的HTTP请求之间 SecurityContext,负责清理在请求完成时 SecurityContextHolder。 清理上下文中保存的ThreadLocal是很基本的,因为它可能 是servlet容器线程池中的一个替换的thread,这样spring security对应一个特定用户 可能出现冲突。这个线程可能被下一个阶段使用,执行操作的时候就会使用错误的证书。

8.3.1. SecurityContextRepository

对于Spring Security 3.0,读取和保存安全上下文的任务被委托给一个单独的策略接口:

public interface SecurityContextRepository {
  SecurityContext loadContext(HttpRequestResponseHolder requestResponseHolder);
  void saveContext(SecurityContext context, HttpServletRequest request,
         HttpServletResponse response);
}

HttpRequestResponseHolder是一个简单的容器,为了进入的请求和相应对象, 允许使用封装类替换它们。返回的内容会被发送给过滤器链。

默认的实现是HttpSessionSecurityContextRepository, 它会把安全上下文保存为一个HttpSession的属性。 [7]. 这个实现中最重要的配置参数是allowSessionCreation属性, 默认是true,因此允许类创建会话,如果它需要保存spring context 为一个认证用户(它不会创建一个除非认证已经执行,security context的内容发生改变)。 如果你不想要创建会话,你可以把这个属性设置为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>
    

可选的是,你可以提供一个SecurityContextRepository接口的 null实现,这就可以防止安全上下文被保存,即使一个session已经在请求期间被创建了。

8.4. UsernamePasswordAuthenticationFilter

我现在看到了三个主要的过滤器,它们会一直在spring security的web配置中起作用。 它们也是由命名空间<http>元素自动创建的三个,它们是无法修改的。 唯一丢失的是一个真正的认证机制,它们会执行一个用户的认证。这个过滤器是最常用的认证过滤器, 这个过滤器也通常需要自定义。 [8]. 它也提供了使用<form-login>元素的实现,来自命名空间。 有三个阶段需要配置它。

  1. 配置一个LoginUrlAuthenticationEntryPoint使用一个登陆页URL, 就像我们上面那样,把它配置到ExceptionTranslationFilter中。

  2. 实现一个登陆页面(使用JSP或MVC控制器)。

  3. 配置一个UsernamePasswordAuthenticationFilter 的实例,放在application context中。

  4. 把过滤器bean添加到你的过滤器链代理中 (确认你注意了顺序)。

登陆表单简单包含了j_usernamej_password输入框, 提交由过滤器管理的URL,(默认为:/j_spring_security_check). 基本的过滤器配置看起来像这样:

<bean id="authenticationFilter" class=
"org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter">
  <property name="authenticationManager" ref="authenticationManager"/>
  <property name="filterProcessesUrl" value="/j_spring_security_check"/>
</bean> 
                

8.4.1. 认证成功和失败的应用流程

过滤器调用了配置的AuthenticationManager处理每个认证请求。 认证成功或失败的目的地是由 AuthenticationSuccessHandlerAuthenticationFailureHandler策略接口各自控制的。 过滤器的属性允许我们设置这些,这样你可以随心所欲的自定义他们的行为。 [9]. 我们提供了一些标准实现,比如SimpleUrlAuthenticationSuccessHandlerSavedRequestAwareAuthenticationSuccessHandlerSimpleUrlAuthenticationFailureHandlerExceptionMappingAuthenticationFailureHandler。 参考这些类的javadoc,了解他们是如何工作的。

如果认证成功,结果Authentication对象会被放到 SecurityContextHolder中。 配置的AuthenticationSuccessHandler会被调用, 进行重定向会把用户跳转到合适的目的地。 默认情况,会使用SavedRequestAwareAuthenticationSuccessHandler。 这意味着,用户会被重定向到原始的目标,他们在登录之前请求的页面。

Note

ExceptionTranslationFilter缓存了一个用户的原始请求。 当用户认证时,请求处理器从这个缓存的请求中获得原始的URL,并重定向到它。 原始请求然后重新构造,作为一个可选项使用。

如果认证失败,配置好的AuthenticationFailureHandler会被调用。



[6] 我们使用forward,这样SecurityContextHolder会包含主体的信息, 这可能对现实用户信息很有帮助。在老版本的Spring Security中,我们让 servlet容器处理一个403错误信息,这可能丢失了有用的上下文信息。

[7] 在spring security 2.0以及以前,这个过滤器叫做 HttpSessionContextIntegrationFilter,它会执行保存上下文 的所有工作。如果你对这个类很熟悉,然后大多数的配置属性现在都可以在 HttpSessionSecurityContextRepository中找到。

[8] 因为一些历史原因,在Spring Security 3.0之前,这个过滤器被称为 AuthenticationProcessingFilter,入口点被称为 AuthenticationProcessingFilterEntryPoint。 因为这个框架现在支持了很多不同的认证表单,它们都需要在3.0中给与更确切的名字。

[9] 在版本3.0之前,这一点的应用流程被当做一个状态,通过这个类的一系列属性 和策略插件进行处理。这个决定让3.0重构了代码,让两个策略完全负责。