spring security

在Web應用程序中的身份驗證

現在讓我們來看看你在Web應用程序中使用Spring Security的情況(不啟用web.xml安全性)。用戶如何進行身份驗證和建立安全環境?

考慮一個典型的Web應用程序的身份驗證過程:

  1. 你訪問首頁, 點擊一個鏈接。

  2. 向服務器發送一個請求,服務器判斷你是否在訪問一個受保護的資源。

  3. 如果你還沒有進行過認證,服務器發回一個響應,提示你必須進行認證。響應可能是HTTP響應代碼,或者是重新定向到一個特定的web頁面。

  4. 依據驗證機制,你的瀏覽器將重定向到特定的web頁面,這樣你可以添加表單,或者瀏覽器使用其他方式校驗你的身份(比如,一個基本校驗對話框,cookie,或者X509證書,或者其他)。

  5. 瀏覽器會發回一個響應給服務器。 這將是HTTP POST包含你填寫的表單內容,或者是HTTP頭部,包含你的驗證信息。

  6. 下一步,服務器會判斷當前的證書是否是有效的, 如果他們是有效的,下一步會執行。 如果他們是非法的,通常你的瀏覽器會再嘗試一次(所以你返回的步驟二)。

  7. 你發送的原始請求,會導致重新嘗試驗證過程。有希望的是,你會通過驗證,得到足夠的授權,訪問被保護的資源。如果你有足夠的權限,請求會成功。否則,你會收到一個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提供了一套一致的工作流程,來處理對安全對象的請求,通常是:

  1. 查找當前請求里分配的"配置屬性"。

  2. 把安全對象,當前的Authentication和配置屬性,提交給AccessDecisionManager來進行以此認證決定。

  3. 有可能在調用的過程中,對Authentication進行修改。

  4. 允許安全對象進行處理(假設訪問被允許了)。

  5. 在調用返回的時候執行配置的AfterInvocationManager。如果調用引發異常,AfterInvocationManager將不會被調用。

配置屬性是什么?

一個"配置屬性"可以看做是一個字符串,它對于AbstractSecurityInterceptor使用的類是有特殊含義的。它們由框架內接口ConfigAttribute表示。它們可能是簡單的角色名稱或擁有更復雜的含義,這就與AccessDecisionManager實現的先進程度有關了。AbstractSecurityInterceptor和配置在一起的 SecurityMetadataSource 用來為一個安全對象搜索屬性。通常這個屬性對用戶是不可見的。配置屬性將以注解的方式設置在受保護方法上,或者作為受保護URLs的訪問屬性。例如,當我們看到像<intercept-url pattern='/secure/**' access='ROLE_A,ROLE_B'/>命名空間中的介紹,這是說配置屬性ROLE_AROLE_B適用于匹配Web請求的特定模式。在實踐中,使用默認的AccessDecisionManager配置, 這意味著,任何人誰擁有GrantedAuthority只要符合這兩個屬性將被允許訪問。嚴格來說,它們只是依賴于AccessDecisionManager實施的屬性和解釋。使用前綴ROLE_是一個標記,以表明這些屬性是角色,應該由Spring Security的RoleVoter前綴被消耗掉。這只是使用AccessDecisionManager的選擇基礎。我們將在授權章看到AccessDecisionManager是如何實現的。

RunAsManager

假設AccessDecisionManager決定允許執行這個請求,AbstractSecurityInterceptor會正常執行這個請求。話雖如此,罕見情況下,用戶可能需要把SecurityContextAuthentication換成另一個Authentication, 這是由AccessDecisionManager 調用RunAsManager。這也許在,有原因,不常見的情況下有用,比如服務層方法需要調用遠程系統表現不同的身份。 因為Spring Security自動傳播安全身份,從一個服務器到另一個(假設你使用了配置好的RMI或者HttpInvoker遠程調用協議客戶端),就可以用到它了。

AfterInvocationManager

按照下面安全對象執行和返回的方式-可能意味著完全的方法調用或過濾器鏈的執行-在AbstractSecurityInterceptor得到一個最后的機會來處理調用。這種狀態下AbstractSecurityInterceptor對有可能修改返回對象感興趣。你可能想讓它發生,因為驗證決定不能“關于如何在”一個安全對象調用。高可插拔性,AbstractSecurityInterceptor通過控制AfterInvocationManager在實際需要的時候修改對象。這里類實際上可能替換對象,或者拋出異常,或者什么也不做。如果調用成功后,檢查調用才會執行。如果出現異常,額外的檢查將被跳過。

AbstractSecurityInterceptor 和它的相關對象 Security interceptors and the "secure object" model

Abstract Security Interceptor

Figure 1. Security interceptors and the "secure object" model

擴展安全對象模型

只有當開發人員考慮一個全新的攔截方法和授權請求時才需要直接使用安全對象。例如,為了確保對消息系統的調用,它有可能建立建立一個新的安全對象。任何東西都需要安全,并且還提供了一種方法去調用(如建議語義的AOP)能夠被做成一個安全對象。不得不說的是,大多數Spring應用程序將只使用三種目前完全支持的安全對象類型(AOP Alliance MethodInvocation, AspectJ JoinPoint和web請求FilterInvocation)。

核心服務

現在,我們對Spring Security的架構和核心類進行高級別的概述,讓我們在一個或兩個核心接口及其實現的仔細看看,尤其是AuthenticationManagerUserDetailsServiceAccessDecisionManager這些東西的信息都在這個文檔的里面,所以這一點很重要,你要知道他們是如何配置如何操作的。

The AuthenticationManager, ProviderManager and AuthenticationProvider

AuthenticationManager只是一個接口,這樣的實現可以是我們選擇的任何東西,但它是如何在實踐中運作的?如果我們需要檢查多個授權數據庫或者將不同的授權服務結合起來,類似數據庫和LDAP服務器?

Spring Security的默認實現被稱為ProviderManager而非處理身份驗證請求本身,它委托給一個列表去配置AuthenticationProvider,其中每個查詢反過來,看它是否能進行認證。每個提供程序都將拋出一個異常或返回一個完全填充的身份驗證對象。還記得我們的好朋友,UserDetailsUserDetailsService嗎?如果沒有,回到前面的章節刷新你的記憶。到驗證的認證請求的最常見的方法是加載相應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的引用,將調用它來處理自己的身份驗證請求。你需要的供應商有時可以與認證機制互換,而在其他時間,他們將依賴于特定的認證機制。例如,DaoAuthenticationProviderLdapAuthenticationProvider給它提交一個簡單的用戶名/密碼驗證請求,并因此將與基于表單登錄或HTTP基本驗證工作的機制兼容。另一方面,一些認證機制創建只能由單一類型AuthenticationProvider解釋的認證請求對象。這一方面的一個例子是JA-SIG CAS,它使用一個服務票據的概念,因此可以僅通過一個CasAuthenticationProvider進行認證。你不必太在意這一點,因為如果你忘記注冊一個合適的供應商,你會簡單地收到一個ProviderNotFoundException不進行認證的嘗試。

清楚成功認證的憑據

默認情況下(從Spring Security 3.1開始)的ProviderManager將試圖清除它返回一個成功的認證請求的Authentication`對象的任何敏感的身份驗證信息。這可以防止密碼等個人資料超過保留時間。

當使用用戶對象的高速緩存時,例如,改善在無狀態情況下應用程序的性能,這可能導致問題。如果Authentication包含在高速緩存(諸如UserDetails實例)的對象的引用中,將其憑證移除,則它將不再能夠進行對緩存的值進行驗證。你需要考慮到這一點,如果你使用的是高速緩存。一個顯而易見的解決方案是讓一個對象的副本,無論是在高速緩存中執行或在AuthenticationProvider它創建返回Authentication對象。另外,你可以在ProviderManager中禁用eraseCredentialsAfterAuthentication。查看Javadoc了解更多信息。

DaoAuthenticationProvider

Spring Security中實現最簡單的AuthenticationProviderDaoAuthenticationProvider,也是最早支持的框架。它利用了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實現

本參考指南早些時候提到的,大多數的認證供應商利用的userdetailsuserdetailsservice接口。回想一下,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獲得如何能夠使用權限組的更多信息。該組架構也包括在附錄中。

  1. 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 the ExceptionTranslationFilter.
  • 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.

|
[Note]

|
|

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.

?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,488評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,034評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,327評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,554評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,337評論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,883評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,975評論 3 439
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,114評論 0 286
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,625評論 1 332
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,555評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,737評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,244評論 5 355
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 43,973評論 3 345
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,362評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,615評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,343評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,699評論 2 370

推薦閱讀更多精彩內容