在Web应用程序中的身份验证
现在让我们来看看你在Web应用程序中使用Spring Security的情况(不启用web.xml
安全性)。用户如何进行身份验证和建立安全环境?
考虑一个典型的Web应用程序的身份验证过程:
你访问首页, 点击一个链接。
向服务器发送一个请求,服务器判断你是否在访问一个受保护的资源。
如果你还没有进行过认证,服务器发回一个响应,提示你必须进行认证。响应可能是HTTP响应代码,或者是重新定向到一个特定的web页面。
依据验证机制,你的浏览器将重定向到特定的web页面,这样你可以添加表单,或者浏览器使用其他方式校验你的身份(比如,一个基本校验对话框,cookie,或者X509证书,或者其他)。
浏览器会发回一个响应给服务器。 这将是HTTP POST包含你填写的表单内容,或者是HTTP头部,包含你的验证信息。
下一步,服务器会判断当前的证书是否是有效的, 如果他们是有效的,下一步会执行。 如果他们是非法的,通常你的浏览器会再尝试一次(所以你返回的步骤二)。
你发送的原始请求,会导致重新尝试验证过程。有希望的是,你会通过验证,得到足够的授权,访问被保护的资源。如果你有足够的权限,请求会成功。否则,你会收到一个HTTP错误代码403,意思是访问被拒绝。
Spring Security使用鲜明的类负责上面提到的每个步骤。主要的部分是(为了使用他们) ExceptionTranslationFilter
, 一个 AuthenticationEntryPoint
一个验证机制, 我们在上一节看到它负责调用AuthenticationManager
。
ExceptionTranslationFilter
ExceptionTranslationFilter
是一个Spring Security过滤器,用来检测是否抛出了Spring Security异常。这些异常会被AbstractSecurityInterceptor
抛出,它主要用来提供验证服务。我们会在下一节讨论AbstractSecurityInterceptor
,但是现在,我们只需要知道,它是用来生成Java,并且要知道和HTTP没什么关系,或者如何验证一个主体。而ExceptionTranslationFilter
提供这些服务,使用特点那个的响应,返回错误代码403(如果主体被验证了,但是权限不足-在上边的步骤七),或者启动一个AuthenticationEntryPoint
(如果主体没有被认证,然后我们需要进入步骤三)。
AuthenticationEntryPoint
AuthenticationEntryPoint
对应上面列表中的步骤三。如你所想的,每个web应用程序都有默认的验证策略(好的,这可以在Spring Security里配置一切,但是让我们现在保持简单)。每个主要验证系统会有它自己的AuthenticationEntryPoint
实现, 会执行动作,如同步骤三里的描述一样。
验证机制
在你的浏览器决定提交你的认证证书之后(使用HTTP表单发送或者是HTTP头),服务器部分需要有一些东西来"收集"这些验证信息。现在我们到了上述的第六步。 在Spring Security里,我们需要一个特定的名字,来描述从用户代码(通常是浏览器)收集验证信息的功能,这个名字就是"验证机制"。实例是窗体的基本登录和基本的身份验证。一旦认证细节已从用户代理收集,建立一个Authentication
"request"对象,然后提交给AuthenticationManager
。
验证机制重新获得了组装好的Authentication
对象时,它会认为请求有效,把Authentication
放到SecurityContextHolder
里的,然后导致原始请求重审(第七步)。另一方面,如果AuthenticationManager
驳回了请求,验证机制会让用户代码重试(第二步)。
Storing the SecurityContext between requests
根据不同的应用程序类型,在用户操作的过程中需要有合适的策略来保存security信息。在一个典型的web应用中,一个用户登录系统之后就会被一个特有的session Id所唯一标识,服务器会将session作用期间的principal数据保存在缓存中。在Spring Security中,保存SecurityContext
的任务落在了SecurityContextPersistenceFilter
身上,它默认将上下文当做HttpSession
属性保存在HTTP请求中,并且将每一个请求的上下文保存在SecurityContextHolder
中,最重要的功能,是在请求结束之后,清理SecurityContextHolder
。你不需要处于安全的目的直接和HttpSession
打交道。在这里仅仅只是不需要那样做-总是使用SecurityContextHolder
来代替HttpSession
。
许多其他的应用(举个例子:一个无状态的RESTful风格web服务)不使用Http Session并且每次请求过来都会进行验证。然而比较重要的是:SecurityContextPersistenceFilter
被包含在过滤器链中,并确保每次请求完毕之后清理SecurityContextHolder
。
| |
其中有一个应用程序接收一个会话的并发请求,同样的SecurityContext
实例将线程之间共享。即使正在使用ThreadLocal
,它是相同的实例,从每个线程的HttpSession
检索。如果你希望暂时改变一个线程正在运行的上下文这很有意义。如果你只是使用SecurityContextHolder.getContext()
,和调用setAuthentication(anAuthentication)
返回的上下文对象,那么Authentication
对象将在全部并发线程共享相同的SecurityContext
情况的变化。 你可以自定义SecurityContextPersistenceFilter
的行为,为每一个请求创建一个完全新的SecurityContext
,防止在一个线程的变化影响另一个。或者,你可以创建一个新的实例,只是在这个点上,你暂时改变了背景。方法SecurityContextHolder.createEmptyContext()
总是返回一个新的上下文实例。
Spring Security的访问控制(授权)
负责Spring Security访问控制决策的主要接口是AccessDecisionManager
。它有一个decide
方法,它需要一个Authentication
对象请求访问,一个"secure object"(见下文)和安全元数据属性的列表适用的对象(如一个列表哪些角色需要被访问授权)。
安全对象和AbstractSecurityInterceptor
那么什么是一个"安全对象"呢?Spring Security使用术语是指可以有安全性的任何对象(如授权决策)应用于它。最常见的例子就是方法调用和web请求。
Spring Security支持的每个安全对象类型都有它自己的类型,他们都是AbstractSecurityInterceptor
的子类。很重要的是,如果主体是已经通过了验证,在AbstractSecurityInterceptor
被调用的时候,SecurityContextHolder
将会包含一个有效的Authentication
。
AbstractSecurityInterceptor
提供了一套一致的工作流程,来处理对安全对象的请求,通常是:
查找当前请求里分配的"配置属性"。
把安全对象,当前的
Authentication
和配置属性,提交给AccessDecisionManager
来进行以此认证决定。有可能在调用的过程中,对
Authentication
进行修改。允许安全对象进行处理(假设访问被允许了)。
在调用返回的时候执行配置的
AfterInvocationManager
。如果调用引发异常,AfterInvocationManager
将不会被调用。
配置属性是什么?
一个"配置属性"可以看做是一个字符串,它对于AbstractSecurityInterceptor
使用的类是有特殊含义的。它们由框架内接口ConfigAttribute
表示。它们可能是简单的角色名称或拥有更复杂的含义,这就与AccessDecisionManager
实现的先进程度有关了。AbstractSecurityInterceptor
和配置在一起的 SecurityMetadataSource
用来为一个安全对象搜索属性。通常这个属性对用户是不可见的。配置属性将以注解的方式设置在受保护方法上,或者作为受保护URLs的访问属性。例如,当我们看到像<intercept-url pattern='/secure/**' access='ROLE_A,ROLE_B'/>
命名空间中的介绍,这是说配置属性ROLE_A
和ROLE_B
适用于匹配Web请求的特定模式。在实践中,使用默认的AccessDecisionManager
配置, 这意味着,任何人谁拥有GrantedAuthority
只要符合这两个属性将被允许访问。严格来说,它们只是依赖于AccessDecisionManager
实施的属性和解释。使用前缀ROLE_
是一个标记,以表明这些属性是角色,应该由Spring Security的RoleVoter
前缀被消耗掉。这只是使用AccessDecisionManager
的选择基础。我们将在授权章看到AccessDecisionManager
是如何实现的。
RunAsManager
假设AccessDecisionManager
决定允许执行这个请求,AbstractSecurityInterceptor
会正常执行这个请求。话虽如此,罕见情况下,用户可能需要把SecurityContext
的Authentication
换成另一个Authentication
, 这是由AccessDecisionManager
调用RunAsManager
。这也许在,有原因,不常见的情况下有用,比如服务层方法需要调用远程系统表现不同的身份。 因为Spring Security自动传播安全身份,从一个服务器到另一个(假设你使用了配置好的RMI或者HttpInvoker远程调用协议客户端),就可以用到它了。
AfterInvocationManager
按照下面安全对象执行和返回的方式-可能意味着完全的方法调用或过滤器链的执行-在AbstractSecurityInterceptor
得到一个最后的机会来处理调用。这种状态下AbstractSecurityInterceptor
对有可能修改返回对象感兴趣。你可能想让它发生,因为验证决定不能“关于如何在”一个安全对象调用。高可插拔性,AbstractSecurityInterceptor
通过控制AfterInvocationManager
在实际需要的时候修改对象。这里类实际上可能替换对象,或者抛出异常,或者什么也不做。如果调用成功后,检查调用才会执行。如果出现异常,额外的检查将被跳过。
AbstractSecurityInterceptor
和它的相关对象 Security interceptors and the "secure object" model
Figure 1. Security interceptors and the "secure object" model
扩展安全对象模型
只有当开发人员考虑一个全新的拦截方法和授权请求时才需要直接使用安全对象。例如,为了确保对消息系统的调用,它有可能建立建立一个新的安全对象。任何东西都需要安全,并且还提供了一种方法去调用(如建议语义的AOP)能够被做成一个安全对象。不得不说的是,大多数Spring应用程序将只使用三种目前完全支持的安全对象类型(AOP Alliance MethodInvocation
, AspectJ JoinPoint
和web请求FilterInvocation
)。
核心服务
现在,我们对Spring Security的架构和核心类进行高级别的概述,让我们在一个或两个核心接口及其实现的仔细看看,尤其是AuthenticationManager
,UserDetailsService
和AccessDecisionManager
这些东西的信息都在这个文档的里面,所以这一点很重要,你要知道他们是如何配置如何操作的。
The AuthenticationManager, ProviderManager and AuthenticationProvider
该 AuthenticationManager
只是一个接口,这样的实现可以是我们选择的任何东西,但它是如何在实践中运作的?如果我们需要检查多个授权数据库或者将不同的授权服务结合起来,类似数据库和LDAP服务器?
Spring Security的默认实现被称为ProviderManager
而非处理身份验证请求本身,它委托给一个列表去配置AuthenticationProvider
,其中每个查询反过来,看它是否能进行认证。每个提供程序都将抛出一个异常或返回一个完全填充的身份验证对象。还记得我们的好朋友,UserDetails
和UserDetailsService
吗?如果没有,回到前面的章节刷新你的记忆。到验证的认证请求的最常见的方法是加载相应UserDetails
并针对已经由用户输入所述一个检查加载密码。这是由DaoAuthenticationProvider
所使用的方法(见下文)。加载的UserDetails
对象-尤其是GrantedAuthority
的IT包含-建设是返回一个成功验证,并存储在SecurityContext
完全填充Authentication
对象时,将被使用。
如果你使用的命名空间,创建并在内部进行维护ProviderManager
的一个实例,您可以通过使用命名空间身份验证提供元素添加提供商。(see 命名空间章节)。在这种情况下,你不应该声明在应用程序上下文中的ProviderManager
bean。但是,如果你没有使用命名空间,那么你会这样声明:
<bean id="authenticationManager"
class="org.springframework.security.authentication.ProviderManager">
<constructor-arg>
<list>
<ref local="daoAuthenticationProvider"/>
<ref local="anonymousAuthenticationProvider"/>
<ref local="ldapAuthenticationProvider"/>
</list>
</constructor-arg>
</bean>
在上面的例子中,我们有三个提供者。它们试图在顺序显示(它是通过使用一个List
的暗示),每个提供者都能尝试验证,或者通过简单的返回null
跳过认证。如果所有的实现都返回null
,则ProviderManager
将抛出一个ProviderNotFoundException
。如果你有兴趣了解更多的有关提供者,请参考ProviderManager
的JavaDocs。
身份验证机,如Web表单登录处理过滤器被注入到ProviderManager
的引用,将调用它来处理自己的身份验证请求。你需要的供应商有时可以与认证机制互换,而在其他时间,他们将依赖于特定的认证机制。例如,DaoAuthenticationProvider
和LdapAuthenticationProvider
给它提交一个简单的用户名/密码验证请求,并因此将与基于表单登录或HTTP基本验证工作的机制兼容。另一方面,一些认证机制创建只能由单一类型AuthenticationProvider
解释的认证请求对象。这一方面的一个例子是JA-SIG CAS,它使用一个服务票据的概念,因此可以仅通过一个CasAuthenticationProvider
进行认证。你不必太在意这一点,因为如果你忘记注册一个合适的供应商,你会简单地收到一个ProviderNotFoundException
不进行认证的尝试。
清楚成功认证的凭据
默认情况下(从Spring Security 3.1开始)的ProviderManager
将试图清除它返回一个成功的认证请求的Authentication`对象的任何敏感的身份验证信息。这可以防止密码等个人资料超过保留时间。
当使用用户对象的高速缓存时,例如,改善在无状态情况下应用程序的性能,这可能导致问题。如果Authentication
包含在高速缓存(诸如UserDetails
实例)的对象的引用中,将其凭证移除,则它将不再能够进行对缓存的值进行验证。你需要考虑到这一点,如果你使用的是高速缓存。一个显而易见的解决方案是让一个对象的副本,无论是在高速缓存中执行或在AuthenticationProvider
它创建返回Authentication
对象。另外,你可以在ProviderManager
中禁用eraseCredentialsAfterAuthentication
。查看Javadoc了解更多信息。
DaoAuthenticationProvider
Spring Security中实现最简单的AuthenticationProvider
是DaoAuthenticationProvider
,也是最早支持的框架。它利用了UserDetailsService
(作为DAO)去查找用户名和密码。它的用户进行身份验证通过userdetailsservice
加载usernamepasswordauthenticationtoken
提交密码进行一对一的比较。配置提供程序是非常简单的:
<bean id="daoAuthenticationProvider"
class="org.springframework.security.authentication.dao.DaoAuthenticationProvider">
<property name="userDetailsService" ref="inMemoryDaoImpl"/>
<property name="passwordEncoder" ref="passwordEncoder"/>
</bean>
这个PasswordEncoder
是可选的。一个PasswordEncoder
提供编码以及UserDetails
对象提出的密码是从配置UserDetailsService
返回的解码。 这将更加详细 如下。
UserDetailsService实现
本参考指南早些时候提到的,大多数的认证供应商利用的userdetails
和userdetailsservice
接口。回想一下,UserDetailsService
是一个方法:
UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
返回的UserDetails
是提供给getters的一个接口,以保证非空的认证信息,例如,用户名,密码,授权和用户帐户是否被启用或禁用。大多数认证供应商将使用UserDetailsService
,即使用户名和密码不作为认证决定的一部分。他们可以使用返回的UserDetails
对象为其GrantedAuthority
信息对象,因为其他的一些系统(如LDAP或X.509或CAS等)承担了实际验证凭证的的责任。
鉴于UserDetailsService
就是这么简单实现的,它应该便于用户检索使用自己选择的持久化策略的认证信息。话虽如此,Spring Security确实包括了许多有用的基本实现,我们将在下面看到。
在内存认证
简单的使用去创建一个自定义的UserDetailsService
实现选择从一个持久性引擎中提取信息,但许多应用程序不需要这么复杂。尤其是如果你正在建设一个原型应用或刚刚开始结合Spring Security当你真的不想花时间配置数据库或写作userdetailsservice
实现。对于这种情况,一个简单的选项是使用安全性 命名空间的user-service
元素:
<user-service id="userDetailsService">
<user name="jimi" password="jimispassword" authorities="ROLE_USER, ROLE_ADMIN" />
<user name="bob" password="bobspassword" authorities="ROLE_USER" />
</user-service>
这也支持一个外部属性文件的使用:
<user-service id="userDetailsService" properties="users.properties"/>
属性文件应包含在表单条目
username=password,grantedAuthority[,grantedAuthority][,enabled|disabled]
例如
jimi=jimispassword,ROLE_USER,ROLE_ADMIN,enabled
bob=bobspassword,ROLE_USER,enabled
JdbcDaoImpl
Spring Security还包括UserDetailsService
,它可以从一个JDBC数据源获得认证信息。内部Spring JDBC的使用,避免了一个全功能对象关系映射(ORM)的复杂性来存储用户信息。如果你的应用程序不使用ORM工具,你可以写一个自定义UserDetailsService
重用在你可能已经创建好的映射文件上。回到 JdbcDaoImpl
,实例的配置如下::
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="org.hsqldb.jdbcDriver"/>
<property name="url" value="jdbc:hsqldb:hsql://localhost:9001"/>
<property name="username" value="sa"/>
<property name="password" value=""/>
</bean>
<bean id="userDetailsService"
class="org.springframework.security.core.userdetails.jdbc.JdbcDaoImpl">
<property name="dataSource" ref="dataSource"/>
</bean>
您可以通过修改上面的DriverManagerDataSource
使用不同的关系型数据库管理系统。你也可以从JNDI获得,与任何其他的Spring配置使用一个全球性的数据源。
Authority Groups
默认情况下,JdbcDaoImpl
加载权限直接映射到用户的角色(见 数据库架构附录)。另一种方法是将权限分成组并分配组给用户。有些人喜欢这种方式作为管理用户权限的一种手段。见 JdbcDaoImpl
Javadoc获得如何能够使用权限组的更多信息。该组架构也包括在附录中。
- Core Security Filters
There are some key filters which will always be used in a web application which uses Spring Security, so we’ll look at these and their supporting classes and interfaces first. We won’t cover every feature, so be sure to look at the Javadoc for them if you want to get the complete picture.
15.1 FilterSecurityInterceptor
We’ve already seen FilterSecurityInterceptor
briefly when discussing access-control in general, and we’ve already used it with the namespace where the <intercept-url>
elements are combined to configure it internally. Now we’ll see how to explicitly configure it for use with a FilterChainProxy
, along with its companion filter ExceptionTranslationFilter
. A typical configuration example is shown below:
<pre class="programlisting" style="line-height: 1.4; color: rgb(0, 0, 0); font-size: 15px; padding: 6px 10px; background-color: rgb(248, 248, 248); border: 1px solid rgb(204, 204, 204); border-radius: 3px; clear: both; overflow: auto; font-family: Consolas, "liberation mono", Courier, monospace;"><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></pre>
FilterSecurityInterceptor
is responsible for handling the security of HTTP resources. It requires a reference to an AuthenticationManager
and an AccessDecisionManager
. It is also supplied with configuration attributes that apply to different HTTP URL requests. Refer back to the original discussion on these in the technical introduction.
The FilterSecurityInterceptor
can be configured with configuration attributes in two ways. The first, which is shown above, is using the <filter-security-metadata-source>
namespace element. This is similar to the <http>
element from the namespace chapter but the <intercept-url>
child elements only use the pattern
and access
attributes. Commas are used to delimit the different configuration attributes that apply to each HTTP URL. The second option is to write your own SecurityMetadataSource
, but this is beyond the scope of this document. Irrespective of the approach used, the SecurityMetadataSource
is responsible for returning a List<ConfigAttribute>
containing all of the configuration attributes associated with a single secure HTTP URL.
It should be noted that the FilterSecurityInterceptor.setSecurityMetadataSource()
method actually expects an instance of FilterInvocationSecurityMetadataSource
. This is a marker interface which subclasses SecurityMetadataSource
. It simply denotes the SecurityMetadataSource
understands FilterInvocation
s. In the interests of simplicity we’ll continue to refer to the FilterInvocationSecurityMetadataSource
as a SecurityMetadataSource
, as the distinction is of little relevance to most users.
The SecurityMetadataSource
created by the namespace syntax obtains the configuration attributes for a particular FilterInvocation
by matching the request URL against the configured pattern
attributes. This behaves in the same way as it does for namespace configuration. The default is to treat all expressions as Apache Ant paths and regular expressions are also supported for more complex cases. The request-matcher
attribute is used to specify the type of pattern being used. It is not possible to mix expression syntaxes within the same definition. As an example, the previous configuration using regular expressions instead of Ant paths would be written as follows:
<pre class="programlisting" style="line-height: 1.4; color: rgb(0, 0, 0); font-size: 15px; padding: 6px 10px; background-color: rgb(248, 248, 248); border: 1px solid rgb(204, 204, 204); border-radius: 3px; clear: both; overflow: auto; font-family: Consolas, "liberation mono", Courier, monospace;"><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></pre>
Patterns are always evaluated in the order they are defined. Thus it is important that more specific patterns are defined higher in the list than less specific patterns. This is reflected in our example above, where the more specific /secure/super/
pattern appears higher than the less specific /secure/
pattern. If they were reversed, the /secure/
pattern would always match and the /secure/super/
pattern would never be evaluated.
15.2 ExceptionTranslationFilter
The ExceptionTranslationFilter
sits above the FilterSecurityInterceptor
in the security filter stack. It doesn’t do any actual security enforcement itself, but handles exceptions thrown by the security interceptors and provides suitable and HTTP responses.
<pre class="programlisting" style="line-height: 1.4; color: rgb(0, 0, 0); font-size: 15px; padding: 6px 10px; background-color: rgb(248, 248, 248); border: 1px solid rgb(204, 204, 204); border-radius: 3px; clear: both; overflow: auto; font-family: Consolas, "liberation mono", Courier, monospace;"><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></pre>
15.2.1 AuthenticationEntryPoint
The AuthenticationEntryPoint
will be called if the user requests a secure HTTP resource but they are not authenticated. An appropriate AuthenticationException
or AccessDeniedException
will be thrown by a security interceptor further down the call stack, triggering the commence
method on the entry point. This does the job of presenting the appropriate response to the user so that authentication can begin. The one we’ve used here is LoginUrlAuthenticationEntryPoint
, which redirects the request to a different URL (typically a login page). The actual implementation used will depend on the authentication mechanism you want to be used in your application.
15.2.2 AccessDeniedHandler
What happens if a user is already authenticated and they try to access a protected resource? In normal usage, this shouldn’t happen because the application workflow should be restricted to operations to which a user has access. For example, an HTML link to an administration page might be hidden from users who do not have an admin role. You can’t rely on hiding links for security though, as there’s always a possibility that a user will just enter the URL directly in an attempt to bypass the restrictions. Or they might modify a RESTful URL to change some of the argument values. Your application must be protected against these scenarios or it will definitely be insecure. You will typically use simple web layer security to apply constraints to basic URLs and use more specific method-based security on your service layer interfaces to really nail down what is permissible.
If an AccessDeniedException
is thrown and a user has already been authenticated, then this means that an operation has been attempted for which they don’t have enough permissions. In this case, ExceptionTranslationFilter
will invoke a second strategy, the AccessDeniedHandler
. By default, an AccessDeniedHandlerImpl
is used, which just sends a 403 (Forbidden) response to the client. Alternatively you can configure an instance explicitly (as in the above example) and set an error page URL which it will forwards the request to [11]. This can be a simple "access denied" page, such as a JSP, or it could be a more complex handler such as an MVC controller. And of course, you can implement the interface yourself and use your own implementation.
It’s also possible to supply a custom AccessDeniedHandler
when you’re using the namespace to configure your application. See the namespace appendix for more details.
15.2.3 SavedRequest s and the RequestCache Interface
Another responsibility of ExceptionTranslationFilter
responsibilities is to save the current request before invoking the AuthenticationEntryPoint
. This allows the request to be restored after the user has authenticated (see previous overview of web authentication). A typical example would be where the user logs in with a form, and is then redirected to the original URL by the default SavedRequestAwareAuthenticationSuccessHandler
(see below).
The RequestCache
encapsulates the functionality required for storing and retrieving HttpServletRequest
instances. By default the HttpSessionRequestCache
is used, which stores the request in the HttpSession
. The RequestCacheFilter
has the job of actually restoring the saved request from the cache when the user is redirected to the original URL.
Under normal circumstances, you shouldn’t need to modify any of this functionality, but the saved-request handling is a "best-effort" approach and there may be situations which the default configuration isn’t able to handle. The use of these interfaces makes it fully pluggable from Spring Security 3.0 onwards.
15.3 SecurityContextPersistenceFilter
We covered the purpose of this all-important filter in the Technical Overview chapter so you might want to re-read that section at this point. Let’s first take a look at how you would configure it for use with a FilterChainProxy
. A basic configuration only requires the bean itself
<pre class="programlisting" style="line-height: 1.4; color: rgb(0, 0, 0); font-size: 15px; padding: 6px 10px; background-color: rgb(248, 248, 248); border: 1px solid rgb(204, 204, 204); border-radius: 3px; clear: both; overflow: auto; font-family: Consolas, "liberation mono", Courier, monospace;"><bean id="securityContextPersistenceFilter"
class="org.springframework.security.web.context.SecurityContextPersistenceFilter"/></pre>
As we saw previously, this filter has two main tasks. It is responsible for storage of the SecurityContext
contents between HTTP requests and for clearing the SecurityContextHolder
when a request is completed. Clearing the ThreadLocal
in which the context is stored is essential, as it might otherwise be possible for a thread to be replaced into the servlet container’s thread pool, with the security context for a particular user still attached. This thread might then be used at a later stage, performing operations with the wrong credentials.
15.3.1 SecurityContextRepository
From Spring Security 3.0, the job of loading and storing the security context is now delegated to a separate strategy interface:
<pre class="programlisting" style="line-height: 1.4; color: rgb(0, 0, 0); font-size: 15px; padding: 6px 10px; background-color: rgb(248, 248, 248); border: 1px solid rgb(204, 204, 204); border-radius: 3px; clear: both; overflow: auto; font-family: Consolas, "liberation mono", Courier, monospace;">public interface SecurityContextRepository {
SecurityContext loadContext(HttpRequestResponseHolder requestResponseHolder);
void saveContext(SecurityContext context, HttpServletRequest request,
HttpServletResponse response);
}</pre>
The HttpRequestResponseHolder
is simply a container for the incoming request and response objects, allowing the implementation to replace these with wrapper classes. The returned contents will be passed to the filter chain.
The default implementation is HttpSessionSecurityContextRepository
, which stores the security context as an HttpSession
attribute [12]. The most important configuration parameter for this implementation is the allowSessionCreation
property, which defaults to true
, thus allowing the class to create a session if it needs one to store the security context for an authenticated user (it won’t create one unless authentication has taken place and the contents of the security context have changed). If you don’t want a session to be created, then you can set this property to false
:
<pre class="programlisting" style="line-height: 1.4; color: rgb(0, 0, 0); font-size: 15px; padding: 6px 10px; background-color: rgb(248, 248, 248); border: 1px solid rgb(204, 204, 204); border-radius: 3px; clear: both; overflow: auto; font-family: Consolas, "liberation mono", Courier, monospace;"><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></pre>
Alternatively you could provide an instance of NullSecurityContextRepository
, a null object implementation, which will prevent the security context from being stored, even if a session has already been created during the request.
15.4 UsernamePasswordAuthenticationFilter
We’ve now seen the three main filters which are always present in a Spring Security web configuration. These are also the three which are automatically created by the namespace <http>
element and cannot be substituted with alternatives. The only thing that’s missing now is an actual authentication mechanism, something that will allow a user to authenticate. This filter is the most commonly used authentication filter and the one that is most often customized [13]. It also provides the implementation used by the <form-login>
element from the namespace. There are three stages required to configure it.
- Configure a
LoginUrlAuthenticationEntryPoint
with the URL of the login page, just as we did above, and set it on theExceptionTranslationFilter
. - Implement the login page (using a JSP or MVC controller).
- Configure an instance of
UsernamePasswordAuthenticationFilter
in the application context - Add the filter bean to your filter chain proxy (making sure you pay attention to the order).
The login form simply contains username
and password
input fields, and posts to the URL that is monitored by the filter (by default this is /login
). The basic filter configuration looks something like this:
<pre class="programlisting" style="line-height: 1.4; color: rgb(0, 0, 0); font-size: 15px; padding: 6px 10px; background-color: rgb(248, 248, 248); border: 1px solid rgb(204, 204, 204); border-radius: 3px; clear: both; overflow: auto; font-family: Consolas, "liberation mono", Courier, monospace;"><bean id="authenticationFilter" class=
"org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter">
<property name="authenticationManager" ref="authenticationManager"/>
</bean></pre>
15.4.1 Application Flow on Authentication Success and Failure
The filter calls the configured AuthenticationManager
to process each authentication request. The destination following a successful authentication or an authentication failure is controlled by the AuthenticationSuccessHandler
and AuthenticationFailureHandler
strategy interfaces, respectively. The filter has properties which allow you to set these so you can customize the behaviour completely [14]. Some standard implementations are supplied such as SimpleUrlAuthenticationSuccessHandler
, SavedRequestAwareAuthenticationSuccessHandler
, SimpleUrlAuthenticationFailureHandler
, ExceptionMappingAuthenticationFailureHandler
and DelegatingAuthenticationFailureHandler
. Have a look at the Javadoc for these classes and also for AbstractAuthenticationProcessingFilter
to get an overview of how they work and the supported features.
If authentication is successful, the resulting Authentication
object will be placed into the SecurityContextHolder
. The configured AuthenticationSuccessHandler
will then be called to either redirect or forward the user to the appropriate destination. By default a SavedRequestAwareAuthenticationSuccessHandler
is used, which means that the user will be redirected to the original destination they requested before they were asked to login.
|
|
The ExceptionTranslationFilter
caches the original request a user makes. When the user authenticates, the request handler makes use of this cached request to obtain the original URL and redirect to it. The original request is then rebuilt and used as an alternative.
|
If authentication fails, the configured AuthenticationFailureHandler
will be invoked.