这儿有几个在web应用中一直会用到的Spring Security关键过滤器, 所以我们回来看看它们,和支持的类和接口。 我们不会覆盖所有功能,所以确认参考它们的javadoc,如果你想要获得完全的信息。
我们已经简要了解了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资源的安全。
它需要一个AuthenticationManager
和
AccessDecisionManager
的引用。
它也需要不同HTTP URL请求的配置属性。
引用回#tech-intro-config-attributes这里可以看到原始信息。
FilterSecurityInterceptor
可是通过两种方式定义配置属性。
第一种,向上面演示的,使用<filter-security-metadata-source>
命名空间元素。这和用来配置FilterChainProxy
的<filter-chain-map>
一样,但是使用的是
<intercept-url>
子元素,只使用pattern
和access
属性。逗号用来分隔不同的配置属性,对于每个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/
就永远也不会执行。
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资源,但是他们还没有被认证。一个对应的
AuthenticationException
或
AccessDeniedException
会被抛出,
由一个安全监听器在下面的调用栈中,触发入口点的
commence
方法。这执行展示对应的响应给用户,
这样认证可以开始。我们这里使用的是
LoginUrlAuthenticationEntryPoint
,它会把请求
重定向到另一个不同的URL(一般是一个登陆页面)。实际的使用使用会依赖
你希望使用到的认证机制。
如果一个用户已经认证了,他再去访问一个被保护的资源时会怎么样呢? 正常情况下,这不会发生,因为应用工作流应该限制哪些资源用户可以访问。 比如一个HTML连接到管理员页面,可能对没有管理权限的用户隐藏。 你不能对一个隐藏的链接点击,因为安全的原因,这总有可能用户直接输入了URL尝试越过限制。 或者他们可能修改了RESTful URL来改变一些参数的值。你的应用必须保护这些场景, 或者它们会变的不安全。你将使用简单的web层安全在你的服务层接口上, 来确认哪些是被允许的。
如果一个AccessDeniedException
被抛出了,
一个用户已经被认证,然后这意味着操作已经被尝试了,而他们没有足够的权限,
这种情况下,ExceptionTranslationFilter
会调用第二个策略,
AccessDeniedHandler
。默认下,一个
AccessDeniedHandlerImpl
会被使用,它只发送一个403(拒绝访问)
响应到客户端。可选的,你可以确切配置一个实例(像上面的例子)然后设置一个错误页URL
它会把请求跳转到错误页。
[6]。
这可能是一个简单的“access denied”页面,比如一个JSP,或者它可能是一个
更加复杂的处理器,比如一个MVC控制器。当然,我们可以实现自己的接口,
使用你自己的实现。
也可能提供一个自定义的AccessDeniedHandler
然后使用命名空间配置你的应用,参考#nsa-access-denied-handler.
我们介绍了所有重要的过滤器,在技术概述一章,
所以你可能希望重新阅读以下这些章节。让我们首先看一下如何使用
FilterChainProxy
配置它们。
一个基本的配置需要bean自己
<bean id="securityContextPersistenceFilter" class="org.springframework.security.web.context.SecurityContextPersistenceFilter"/>
像我们之前看到的,这个过滤器有两个主要的任务,它负责保存在不同的HTTP请求之间
SecurityContext
,负责清理在请求完成时
SecurityContextHolder
。
清理上下文中保存的ThreadLocal
是很基本的,因为它可能
是servlet容器线程池中的一个替换的thread,这样spring security对应一个特定用户
可能出现冲突。这个线程可能被下一个阶段使用,执行操作的时候就会使用错误的证书。
对于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已经在请求期间被创建了。
我现在看到了三个主要的过滤器,它们会一直在spring security的web配置中起作用。
它们也是由命名空间<http>
元素自动创建的三个,它们是无法修改的。
唯一丢失的是一个真正的认证机制,它们会执行一个用户的认证。这个过滤器是最常用的认证过滤器,
这个过滤器也通常需要自定义。
[8].
它也提供了使用<form-login>
元素的实现,来自命名空间。
有三个阶段需要配置它。
配置一个LoginUrlAuthenticationEntryPoint
使用一个登陆页URL,
就像我们上面那样,把它配置到ExceptionTranslationFilter
中。
实现一个登陆页面(使用JSP或MVC控制器)。
配置一个UsernamePasswordAuthenticationFilter
的实例,放在application context中。
把过滤器bean添加到你的过滤器链代理中 (确认你注意了顺序)。
登陆表单简单包含了j_username
和j_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>
过滤器调用了配置的AuthenticationManager
处理每个认证请求。
认证成功或失败的目的地是由
AuthenticationSuccessHandler
和
AuthenticationFailureHandler
策略接口各自控制的。
过滤器的属性允许我们设置这些,这样你可以随心所欲的自定义他们的行为。
[9].
我们提供了一些标准实现,比如SimpleUrlAuthenticationSuccessHandler
,
SavedRequestAwareAuthenticationSuccessHandler
,
SimpleUrlAuthenticationFailureHandler
和
ExceptionMappingAuthenticationFailureHandler
。
参考这些类的javadoc,了解他们是如何工作的。
如果认证成功,结果Authentication
对象会被放到
SecurityContextHolder
中。
配置的AuthenticationSuccessHandler
会被调用,
进行重定向会把用户跳转到合适的目的地。
默认情况,会使用SavedRequestAwareAuthenticationSuccessHandler
。
这意味着,用户会被重定向到原始的目标,他们在登录之前请求的页面。
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重构了代码,让两个策略完全负责。