安全的複雜之處:安全web請求的架構

         藉助於Spring Security的強大基礎配置功能以及內置的認證功能,咱們在前面講述的三步配置是很快就能完成的;它們的使用是經過添加auto-config屬性和http元素實現的。

         但不幸的是,應用實現的考量、架構的限制以及基礎設施集成的要求可能使你的Spring Security實現遠較這個簡單的配置所提供的複雜。不少用戶一使用比基本配置複雜的功能就會遇到麻煩,那是由於他們不瞭解這個產品的架構以及各個元素是如何協同工做以實現一個總體的。 java

         理解web請求的總體流程以及它們是如何穿越實現功能的攔截器鏈,對咱們成功瞭解Spring Security的高級話題相當重要。記住認證和受權的基本概念,由於它們貫穿咱們要保護的系統架構的始終。 web


請求是怎樣被處理的?

         Spring Security的架構在很大程度上依賴代理(delegates)和servlet過濾器,來實現環繞在web應用請求先後的功能層。 spring

         Servlet過濾器(Servlet Filter,實現javax.servlet.Filter接口的類)被用來攔截用戶請求來進行請求以前或以後的處理,或者乾脆重定向這個請求,這取決於servlet過濾器的功能。在JBCP Pets在線商店中,最終的目標servlet是Spring MVC 分發servlet,可是在理論上它多是任何一個web servlet。下面的圖描述了一個servlet過濾器是如何封裝一個用戶請求的: 數據庫



 Spring Security在XML配置文件中的自動配置屬性,創建了十個servlet過濾器,它們經過使用Java EE的servlet過濾器鏈按順序組合起來。Filter chain是Java EE Servlet API的一個概念,經過接口javax.servlet.FilterChain進行定義,它容許在web應用中的一系列的servlet過濾器可以應用於任何給定的請求。 express

         與生活中金屬製定的鏈相似,每個servelt過濾器表明鏈上的一環,它會進行方法的調用以處理用戶的請求。請求穿過整個過濾器鏈,按順序調用每一個過濾器。 api



 正如你能從鏈這個詞彙中推斷出的那樣,servlet請求按照必定的順序從一個過濾器到下一個穿過整個過濾器鏈,最終到達目標servlet。與之相對的是,當servelt處理完請求並返回一個response時,過濾器鏈按照相反的順序再次穿過全部的過濾器。 安全

         Spring Security使用了過濾器鏈的概念並實現了本身抽象,提供了VirtualFilterChain,它能夠根據Spring Security XML配置文件中設置的URL模式動態的建立過濾器鏈(能夠將它與標準的Java EE過濾器鏈進行對比,後者須要在web應用的部署描述文件中進行設置)。 網絡

【Servlet過濾器除了可以如它的名字所描述的那樣進行過濾功能(或阻止請求)之外,還能夠用於不少其餘的目的。實際上,不少的servlet過濾器的功能相似於在web運行的環境中對請求進行AOP式的代理攔截,由於它們能夠容許一些功能在任何發往servelt容器的請求處理以前或以後執行。過濾器能實現的多功能在Spring Security中頁獲得了體現,由於不少過濾器實際上並不直接影響用戶的請求。】 session

 

自動配置的選項爲你創建了十個Spring Security的過濾器。理解這些過濾器的默認行爲以及它們在哪裏以及如何配置的,對使用Spring Security的高級功能相當重要。 架構

 

這些過濾器以及它們使用的順序,在下面的表格中進行了描述。大多數這些過濾器在咱們完善JBCP Pets在線商店的過程當中都會被再次提到,因此若是你如今不明白它們的確切功能也沒必要擔憂。

過濾器名稱

描述

o.s.s.web.context.SecurityContextPersistenceFilter

負責從SecurityContextRepository獲取或存儲SecurityContext。SecurityContext表明了用戶安全和認證過的session。

o.s.s.web.authentication.logout.LogoutFilter

監控一個實際爲退出功能的URL(默認爲/j_spring_security_logout),而且在匹配的時候完成用戶的退出功能。

o.s.s.web.authentication.UsernamePasswordAuthenticationFilter

監控一個使用用戶名和密碼基於form認證的URL(默認爲/j_spring_security_check),並在URL匹配的狀況下嘗試認證該用戶。

o.s.s.web.authentication.ui.DefaultLoginPageGeneratingFilter

監控一個要進行基於forn或OpenID認證的URL(默認爲/spring_security_login),並生成展示登陸form的HTML

o.s.s.web.authentication.www.BasicAuthenticationFilter

監控HTTP 基礎認證的頭信息並進行處理

o.s.s.web.savedrequest.

RequestCacheAwareFilter

用於用戶登陸成功後,從新恢復由於登陸被打斷的請求。

o.s.s.web.servletapi.

SecurityContextHolderAwareRequest

Filter

用一個擴展了HttpServletRequestWrapper的子類(o.s.s.web. servletapi.SecurityContextHolderAwareRequestWrapper)包裝HttpServletRequest。它爲請求處理器提供了額外的上下文信息。

o.s.s.web.authentication.

AnonymousAuthenticationFilter

若是用戶到這一步尚未通過認證,將會爲這個請求關聯一個認證的token,標識此用戶是匿名的。

o.s.s.web.session.

SessionManagementFilter

根據認證的安全實體信息跟蹤session,保證全部關聯一個安全實體的session都能被跟蹤到。

o.s.s.web.access.

ExceptionTranslationFilter

解決在處理一個請求時產生的指定異常

o.s.s.web.access.intercept.

FilterSecurityInterceptor

簡化受權和訪問控制決定,委託一個AccessDecisionManager完成受權的判斷

Spring Security擁有總共大約25個過濾器,它們可以根據你的須要進行適當的應用以改變用戶請求的行爲。固然,若是須要的話,你也能夠添加你本身實現了javax.servlet.Filter接口的過濾器。

請記住,若是你在XML配置文件中使用了auto-config屬性,以上表格中列出的過濾器自動添加的。經過使用一些額外的配置指令,以上列表中的過濾器可以精確的控制是否被包含,在後續的章節章將會進行介紹。

 

你可能會徹底從頭作起來配置過濾器鏈。儘管這會很單調乏味,由於有不少的依賴關係要配置,可是它爲配置和應用場景的匹配方面提供了更高層次的靈活性。咱們將在第六章講述在啓動的過程當中所依賴的Spring Bean的聲明。

你可能想知道DelegatingFilterProxy是怎樣找到Spring Security配置的過濾器鏈的。讓咱們回憶一下,在web.xml文件中,咱們須要給DelegatingFilterProxy一個過濾器的名字:

<filter>

  <filter-name>springSecurityFilterChain</filter-name>

  <filter-class>

    org.springframework.web.filter.DelegatingFilterProxy

  </filter-class>

</filter>

這個過濾器的名字並非隨意配置的,實際上會跟根據這個名字把Spring Security織入到DelegatingFilterProxy。除非明確配置,不然DelegatingFilterProxy會在Spring WebApplicationContext中尋找同名的配置bean(名字是在filter-name中指明的)。更多配置DelegatingFilterProxy的細節能夠在這個類對應的Javadoc中找到。

auto-config場景下,發生了什麼事情?

在Spring Security 3中,使用auto-config會自動提供如下三個認證相關的功能:

<!--[if !supportLists]-->l  <!--[endif]-->HTTP基本認證

<!--[if !supportLists]-->l  <!--[endif]-->Form登陸認證

<!--[if !supportLists]-->l  <!--[endif]-->退出

值得注意的是,也可使用配置元素實現這三個功能,可以實現比使用auto-config提供的功能更精確。咱們將在隨後的章節中看到它們的使用以提供更高級的功能。

【auto-config和之前不同了!在Spring Security3以前的版本中,auto-config屬性提供了比如今更多的啓動項。在Spring Security2中經過auto-config配置的功能,如今可使用security命名空間樣式的配置很容易的實現。請參考13章:遷移至Spring Security 3來獲取更多從Spring Security2遷移到3的詳細信息。】

除了以上認證相關的功能,其它過濾器鏈的配置是經過使用<http>元素來實現的。

 

用戶是怎樣認證的?

在咱們的安全系統中,當一個用戶在咱們的登陸form中提供憑證後,這些憑證信息必須與憑證存儲中的數據進行校驗以肯定下一步的行爲。憑證的校驗涉及到一系列的邏輯組件,它們封裝了認證過程。

咱們將會深刻講解咱們例子中的用戶名和密碼登陸form,與之對應的接口和實現都是特定於用戶名和密碼認證的。可是,請記住,總體的認證是相同的,無論你是使用基於form的登陸請求,或者使用一個外部的認證提供者如集中認證服務(CAS),抑或用戶的憑證信息存在一個數據庫或在一個LDAP目錄中。在本書的第二部分,咱們將會看到在基於form登陸中學到的概念是如何應用到更高級的認證機制中。

涉及到認證功能的重要接口在下邊的圖標中有一個概覽性的描述:



 站在一個較高層次上看,你能夠看到有三個主要的組件負責這項重要的事情:

接口名

描述/角色

AbstractAuthenticationProcessingFilter

它在基於web的認證請求中使用。處理包含認證信息的請求,如認證信息多是form POST提交的、SSO信息或者其餘用戶提供的。建立一個部分完整的Authentication對象以在鏈中傳遞憑證信息。

AuthenticationManager

它用來校驗用戶的憑證信息,或者會拋出一個特定的異常(校驗失敗的狀況)或者完整填充Authentication對象,將會包含了權限信息。

AuthenticationProvider

它爲AuthenticationManager提供憑證校驗。一些AuthenticationProvider的實現基於憑證信息的存儲,如數據庫,來斷定憑證信息是否能夠被承認。

有兩個重要接口的實現是在認證鏈中被這些參與的類初始化的,它們用來封裝一個認證過(或尚未認證過的)的用戶的詳細信息和權限。

o.s.s.core.Authentication是你之後要常常接觸到的接口,由於它存儲了用戶的詳細信息,包括惟一標識(如用戶名)、憑證信息(如密碼)以及本用戶被授予的一個或多個權限(o.s.s.core.

GrantedAuthority)。開發人員一般會使用Authentication對象來獲取用戶的詳細信息,或者使用自定義的認證明現以便在Authentication對象中增長應用依賴的額外信息。

如下列出了Authentication接口能夠實現的方法:

方法簽名

描述

Object getPrincipal()

返回安全實體的惟一標識(如,一個用戶名)

Object getCredentials()

返回安全實體的憑證信息

List<GrantedAuthority>

getAuthorities()

獲得安全實體的權限集合,根據認證信息的存儲決定的。

Object getDetails()

返回一個跟認證提供者相關的安全實體細節信息

你可能會擔憂的發現,Authentication接口有好幾個方法的返回值是簡單的java.lang.Object。這可能會致使在編譯階段很難知道調用Authentication對象的方法返回值是什麼類型的對象。

須要注意的一點是AuthenticationProvider並非直接被AuthenticationManager接口使用或引用的。可是Spring Security只提供了AuthenticationManager的一個具體實現類,即o.s.s.authentication.ProviderManager,它會使用一個或更多以上描述的AuthenticationProvider實現類。由於AuthenticationProvider的使用很是廣泛而且被很好的集成在ProviderManager中,因此理解它在最多見的基本配置下是如何工做的就很是重要了。

讓咱們更仔細的看看在基於web用戶名和密碼認證的請求下,這些類的處理過程:

 

 



 
讓咱們看一下在較高層次示意圖中反映出的抽象工做流程,並將其細化到這個基於表單認證的具體實現。你能夠看到UsernamePasswordAuthenticationFilter負責(經過代理從它的抽象父類中)建立UsernamePasswordAuthenticationToken對象(Authentication接口的一個實現),並部分填充這個對象依賴的信息,這些信息來自HttpServletRequet。可是它是從哪裏獲取用戶名和密碼的呢?

spring_security_login是什麼?咱們怎麼到達這個界面的?

 你可能已經發現,當你試圖訪問咱們JBCP Pets商店的主頁時,你被重定向到http://localhost:8080/JBCPPets/spring_security_login



 URL的spring_security_login部分代表這是一個默認的登陸的頁面而且是在DefaultLoginPageGeneratingFilter中命名的。咱們可使用配置屬性來修改這個頁面的名字從而使得它對於咱們應用來講是惟一的。

【建議修改登陸頁URL的默認值。修改後不只可以對應用或搜索引擎更友好,並且可以隱藏你使用Spring Security做爲安全實現的事實。經過這種方式來掩蓋Spring Security可以使得萬一Spring Security被發現存在安全漏洞時,惡意黑客尋找你應用漏洞的難度。儘管經過這種方式的安全掩蓋不會下降你應用的脆弱性,可是它確實可以使得一些傳統的黑客工具很難肯定你的應用可以承擔的住什麼類型的攻擊。須要注意的是,這裏並非「spring」名稱在URL中出現的惟一地方。咱們將在後面的章節詳細闡述。】

讓咱們看一下這個form的HTML源碼(忽略佈局信息),來看一下UsernamePasswordAuthenticationFilter指望獲得的信息:

 

<form name='f' action='/JBCPPets/j_spring_security_check'  method='POST'>

  User:<input type='text' name='j_username' value=''>

  Password:<input type='password' name='j_password'/>

  <input name="submit" type="submit"/>

  <input name="reset" type="reset"/>

</form>

你能夠看到用戶名和密碼對應的form文本域有獨特的名字((j_username和j_password),而且form的action地址j_spring_security_check也並非咱們配置的。它們是怎麼來的呢?

文本域的名字是UsernamePasswordAuthenticationFilter規定的,並借鑑了Java EE Servlet 2.x的規範(在SRV.12.5.3章節中),規範要求登陸的form使用特定的名字而且form的action要爲特定的j_security_check值。這樣的實際模式目標是容許基於Java EE servlet-based的應用可以與servlet容器的安全設施以標準的方式鏈接起來。

由於咱們的應用沒有使用到servlet容器的安全組件,因此能夠明確設置UsernamePasswordAuthenticationFilter以使得文本域有不一樣的名字。這種特定的配置變化可能會比你想象的複雜。如今,咱們將要回顧一下UsernamePasswordAuthenticationFilter的生命週期,看一下它是如何進入咱們配置的(儘管咱們將會在第六章再次講述這個配置)。

UsernamePasswordAuthenticationFilter是經過<http>配置指令的<form-login>子元素來進行配置的。正如在本章前面講述的,咱們設置的auto-config元素將會在你沒有明確添加的狀況下包含了<form-login>功能。正如你可能猜想的那樣,j_spring_security_check並不對應任何應用中的物理資源。它只是UsernamePasswordAuthenticationFilter監視的一個基於form登陸的URL。實際上,在Spring Security中有好幾個這樣的特殊的URL來實現特定的全局功能。你能在附錄:參考資料中找到這些URL的一個列表。

 

用戶的憑證信息是在哪裏被校驗的?

在咱們的簡單的三步配置文件中,咱們使用了一個基於內存的憑證存儲實現快速的部署和運行:

 

<authentication-manager alias="authenticationManager">

  <authentication-provider>

    <user-service>

      <user authorities="ROLE_USER" name="guest" password="guest"/>

    </user-service>

  </authentication-provider>

</authentication-manager>

咱們沒有將AuthenticationProvider與任何具體的實現相關聯,在這裏咱們再次看到了security命名空間默認爲咱們作了許多機械的配置工做。可是須要記住的是AuthenticationManager支持配置一個或多個AuthenticationProvider。<authentication-provider>聲明默認誰實例化一個內置的實現,即o.s.s.authentication.dao.DaoAuthenticationProvider。<authentication-provider>聲明會自動的將這個AuthenticationProvider對象織入到配置的AuthenticationManager中,固然在咱們這個場景中AuthenticationManager是自動配置的。

DaoAuthenticationProvider是AuthenticationProvider的簡單封裝實現並委託o.s.s.core.userdetails.UserDetailsService接口的實現類進行處理。UserDetailsService負責返回o.s.s.core.userdetails.UserDetails的一個實現類。

若是你查看UserDetails的Javadoc,你會發現它與咱們前面討論的Authentication接口很是相似。儘管它們在方法名和功能上有些重疊的部分,可是請不要混淆,它們有着大相徑庭的目的:

接口

目的

Authentication

它存儲安全實體的標識、密碼以及認證請求的上下文信息。它還包含用戶認證後的信息(可能會包含一個UserDetails的實例)。一般不會被擴展,除非是爲了支持某種特定類型的認證。

UserDetails

爲了存儲一個安全實體的概況信息,包含名字、e-mail、電話號碼等。一般會被擴展以支持業務需求。

咱們對<user-service>子元素的聲明將會觸發對o.s.s.core.userdetails.memory.InMemoryDaoImpl的配置,它是UserDetailsService的一個實現。正如你可能指望的那樣,這個實現將在安全XML文件中配置的用戶信息放在一個內存的數據存儲中。這個service的實現支持其它屬性的設置,從而實現帳戶的禁用和鎖定。

讓咱們更直觀的看一下DaoAuthenticationProvider是如何交互的,從而AuthenticationManager提供認證支持:



 正如你可能想象的那樣,認證是至關可配置化的。大多數的Spring Security例子要麼使用基於內存的用戶憑證存儲要麼使用JDBC(在數據庫中)的用戶憑證存儲。咱們已經意識到修改JBCP Pets應用以實現數據庫存儲用戶憑證是一個好主意,咱們將會在第四章來處理這個配置變化。

何時校驗不經過?

Spring Security很好的使用應用級異常(expected exceptions)來表示處理各類的結果狀況。你可能在使用Spring Security的平常工做中不會與這些異常打交道,可是瞭解它們以及它們爲什麼被拋出將會在調試問題或理解應用流程中很是有用。

全部認證相關的異常都繼承自o.s.s.core.AuthenticationException基類。除了支持標準的異常功能,AuthenticationException包含兩個域,可能在提供調試失敗信息以及報告信息給用戶方面頗有用處。

<!--[if !supportLists]-->l  <!--[endif]-->authentication:存儲關聯認證請求的Authentication實例;

<!--[if !supportLists]-->l  <!--[endif]-->extraInformation:根據特定的異常能夠存儲額外的信息。如UsernameNotFoundException在這個域上存儲了用戶名。

咱們在下面的表格中,列出了常見的異常。完整的認證異常列表能夠在附錄:參考資料中找到:

異常類

什麼時候拋出

extraInformation內容

BadCredentialsException

如何沒有提供用戶名或者密碼與認證存儲中用戶名對應的密碼不匹配

UserDetails

LockedException

若是用戶的帳號被發現鎖定了

UserDetails

UsernameNotFoundException

若是用戶名不存在或者用戶沒有被授予的GrantedAuthority

String(包含用戶名)

這些以及其餘的異常將會傳遞到過濾器鏈上,一般將會被request請求的過濾器捕獲並處理,要麼將用戶重定向到一個合適的界面(登陸或訪問拒絕),要麼返回一個特殊的HTTP狀態碼,如HTTP 403(訪問被拒絕)。


請求是怎樣被受權的?

在Spring Security的默認過濾器鏈中,最後一個servelt過濾器是FilterSecurityInterceptor,它的做用是判斷一個特定的請求是被容許仍是被拒絕。在FilterSecurityInterceptor被觸發的時候,安全實體已經通過了認證,因此係統知道他們是合法的用戶。(其實也有多是匿名的用戶,譯者注)。請記住的一點是,Authentication提供了一個方法((List<GrantedAuthority>

getAuthorities()),將會返回當前安全實體的一系列權限列表。受權的過程將使用這個方法提供的信息來決定一個特定的請求是否會被容許。

 

須要記住的是受權是一個二進制的決策——一個用戶要麼有要麼沒有訪問一個受保護資源的權限。在受權中,沒有模棱兩可的情景。

在Spring Security中,良好的面向對象設計隨處可見,在受權決策管理中也不例外。回憶一下咱們在本章前面的討論,一個名爲訪問控制決策器(access decision manager)的組件負責做出受權決策。

在Spring Security中,o.s.s.access.AccessDecisionManager接口定義了兩個簡單而合理的方法,它們可以用於請求的決策判斷流程:

<!--[if !supportLists]-->l  <!--[endif]-->supports:這個邏輯操做實際上包含兩個方法,它們容許AccessDecisionManager的實現類判斷是否支持當前的請求。

<!--[if !supportLists]-->l  <!--[endif]-->decide:基於請求的上下文和安全配置,容許AccessDecisionManager去核實訪問是否被容許以及請求是否可以被接受。decide方法實際上沒有返回值,經過拋出異常來代表對請求訪問的拒絕。

 

與AuthenticationException及其子類在認證過程當中的使用很相似,特定類型的異常可以代表應用在受權決策中的不一樣處理結果。o.s.s.access.AccessDeniedException是在受權領域裏最多見的異常,所以值得過濾器鏈進行特殊的處理。咱們將在第六章中詳細介紹它的高級配置。

AccessDecisionManager是可以經過標準的Spring bean綁定和引用實現徹底的自定義配置。AccessDecisionManager的默認實現提供了一個基於AccessDecisionVoter接口和投票集合的受權機制。

投票器(voter)是在受權過程當中的一個重要角色,它的做用是評估如下的內容:

<!--[if !supportLists]-->l  <!--[endif]-->要訪問受保護資源的請求所對應上下文(如URL請求的IP地址);

<!--[if !supportLists]-->l  <!--[endif]-->用戶的憑證信息(若是存在的話);

<!--[if !supportLists]-->l  <!--[endif]-->要試圖訪問的受保護資源;

<!--[if !supportLists]-->l  <!--[endif]-->系統的配置以及要訪問資源自己的配置參數。

AccessDecisionManager還會負責傳遞要請求資源的訪問聲明信息(在代碼中爲ConfigAttribute接口的實現類)給投票器。在web URL的請求中,投票器將會獲得資源的訪問聲明信息。若是看一下咱們配置文件中很是基礎的攔截聲明,咱們可以看到ROLE_USER被設置爲訪問配置並用於用戶試圖訪問的資源:

 

<intercept-url pattern="/*" access="ROLE_USER"/>

投票器將會對用戶是否可以訪問指定的資源作出一個判斷。Spring Security容許過濾器在三種決策結果中作出一種選擇,它們的邏輯定義在o.s.s.access.AccessDecisionVoter接口中經過常量進行了定義。

決策類型

描述

Grant (ACCESS_GRANTED)

投票器容許對資源的訪問

Deny (ACCESS_DENIED)

投票器拒絕對資源的訪問

Abstain (ACCESS_ABSTAIN)

投票器對是否可以訪問作了棄權處理(即沒有作出決定)。可能在多種緣由下發生,如:

<!--[if !supportLists]-->l  <!--[endif]-->投票器沒有確鑿的判斷信息;

<!--[if !supportLists]-->l  <!--[endif]-->投票器不能對這種類型的請求作出決策。

正如你從訪問決策相關類和接口的設計中能夠猜到的那樣,Spring Security的這部分被精心設計,因此認證和訪問控制的使用場景並不只僅限於web領域。咱們將會在:精確的訪問控制中關於方法級別的安全時,再次講解投票器和訪問控制管理。

當將他們組合在一塊兒,「對web請求的默認認證檢查」的總體流程將以下圖所示:



 咱們能夠看到ConfigAttribute可以從配置聲明(在DefaultFilterInvocationSecurityMetadataSource類中保存)中傳遞數據到投票器,投票器並不須要其餘的類來理解ConfigAttribute的內容。這種分離可以爲新類型的安全聲明(例如咱們將要看到的方法安全聲明)使用相同的訪問決策模式提供基礎。

配置access decision集合

實際上Spring Security容許經過security命名空間來配置AccessDecisionManager。<http>元素的access-decision-manager-ref屬性來指明一個實現了AccessDecisionManager的Spring Bean。Spring Security提供了這個接口的三個實現類,都在o.s.s.access.vote包中:

類名

描述

AffirmativeBased

若是有任何一個投票器容許訪問,請求將被馬上容許,而無論以前可能有的拒絕決定。

ConsensusBased

多數票(容許或拒絕)決定了AccessDecisionManager的結果。平局的投票和空票(全是棄權的)的結果是可配置的。

UnanimousBased

全部的投票器必須全是容許的,不然訪問將被拒絕。

 

配置使用UnanimousBased的訪問決策管理器(access decision manager)

若是你想修改咱們的應用來使用UnanimousBased訪問決策管理器,咱們須要修改兩個地方。首先讓咱們在<http>元素上添加access-decision-manager-ref屬性:

 

<http auto-config="true"

      access-decision-manager-ref="unanimousBased" >

這是一個標準的Spring Bean的引用,因此這須要對應一個bean的id屬性。接下來,咱們要定義這個bean(在dogstore-base.xml中),並與咱們引用的有相同的id:

 

<bean class="org.springframework.security.access.vote.UnanimousBased"

      id="unanimousBased">

  <property name="decisionVoters">

    <list>

      <ref bean="roleVoter"/>

      <ref bean="authenticatedVoter"/>

    </list>

  </property>

</bean>

<bean class="org.springframework.security.access.vote.RoleVoter"

id="roleVoter"/>

<bean class="org.springframework.security.access.vote.

AuthenticatedVoter" id="authenticatedVoter"/>

你可能象知道decisionVoters屬性是什麼。這個屬性在咱們不聲明AccessDecisionManager時,是自動配置的。默認的AccessDecisionManager要求咱們配置投票器的一個列表,它們將會在認證決策時用到。這裏列出的兩個投票器是security命名空間配置默認提供的。

遺憾的是,Spring Security沒有爲咱們提供太多的投票器,可是實現AccessDecisionVoter接口並在配置中添加咱們的實現並非一件困難的事情。咱們將在第六章看一個例子。

咱們引用的兩個投票器介紹以下:

類名

描述

例子

o.s.s.access.

vote.RoleVoter

檢查用戶是否擁有聲明角色的權限(GrantedAuthority)。access屬性定義了GrantedAuthority的一個列表。預期會有ROLE_前綴,但這也是可配置的。

access="ROLE_USER,ROLE_ADMIN"

o.s.s.access.

vote.AuthenticatedVoter

支持特定類型的聲明,容許使用通配符:

<!--[if !supportLists]-->l  <!--[endif]-->IS_AUTHENTICATED_FULLY——容許提供完整的用戶名和密碼的用戶訪問;

<!--[if !supportLists]-->l  <!--[endif]-->IS_AUTHENTICATED_REMEMBERED——若是用戶是經過remember me功能認證的則容許訪問;

<!--[if !supportLists]-->l  <!--[endif]-->IS_AUTHENTICATED_ANONYMOUSLY——容許匿名用戶訪問。

access=" IS_AUTHENTICATED_ANONYMOUSLY"

使用 Spring 表達式語言配置訪問控制

基於角色標準投票機制的標準實現是使用 RoleVoter ,還有一種替代方法可用來定義語法複雜的投票規則即便用Spring 表達式語言( SpEL )。要實現這一功能的直接方式是在 <http> 配置元素上添加 use-expressions 屬性:

 

<http auto-config="true"

      use-expressions="true">

添加後將要修改用來進行攔截器規則聲明的 access 屬性,改成 SpEL 表達式。 SpEL 容許使用特定的訪問控制規則表達式語言。與簡單的字符串如 ROLE_USER 不一樣,配置文件能夠指明表達式語言觸發方法調用、引用系統屬性、計算機值等等。

SpEL 的語法與其餘的表達式語言很相似,如在 Tapestry 等框架中用到的 Object Graph Notation Language (OGNL) ,以及用於 JSP 和 JSF 的 Unified Expression Language 。它的語法面很廣,已經超出了本書的覆蓋範圍,咱們將會經過幾個例子爲你構建表達式提供一些確切的幫助。

須要注意的重要一點是,若是你經過使用 use-expressions 屬性啓用了 SpEL 表達式訪問控制,將會使得自動配置的 RoleVoter 實效,後者可以使用角色的聲明,正如在前面的例子所見到的那樣:

 

<intercept-url pattern="/*" access="ROLE_USER"/>

這意味着若是你僅僅想經過角色來過濾請求的話,訪問控制聲明必要要進行修改。幸運的的是,這已經被充分考慮過了,一個 SpEL 綁定的方法 hasRole 可以檢查角色。若是咱們要使用表達式來重寫例子的配置,它可能看起來以下所示:

 

<http auto-config="true" use-expressions="true">

  <intercept-url pattern="/*" access="hasRole('ROLE_USER')"/>

</http>

正如你可能預料的那樣, SpEL 使用了一個不一樣的 Voter 實現類,即o.s.s.web.access.expression.WebExpressionVoter ,它能理解怎樣解析 SpEL 表達式。 WebExpressionVoter 藉助於 o.s.s.web.access.expression.WebSecurityExpressionHandler 接口的一個實現類來達到這個目的。WebSecurityExpressionHandler 同時負責評估表達式的執行結果以及提供在表達式中應用的安全相關的方法。這個接口的默認實現對外暴露了 o.s.s.web.access.expression.WebSecurityExpressionRoot 類中定義的方法。

這些類的流程以及關係以下圖所示:



 爲實現 SpEL 訪問控制表達式的方法和僞屬性( pseudo-property )在類 WebSecurityExpessionRoot 及其父類的公共方法中進行了聲明。

【僞屬性( pseudo-property )是指沒有傳入參數並符合 JavaBeans 的 getters 命名格式的方法。這容許 SpEL 表達式可以省略方法的圓括號以及 is 或 get 的前綴。如 isAnonymous() 方法能夠經過 anonymous 僞屬性來訪問。】

Spring Security 3 提供的 SpEL 方法和僞屬性在如下的表格中進行了描述。要注意的是沒有被標明「 web only 」的方法和屬性能夠在保護其餘類型的資源中使用,如在保護方法調用時。示例表示的方法和屬性是使用在 <intercept-url> 的 access 聲明中。

方法

Web only?

描述

示例

hasIpAddress

(ipAddress)

Yes

用於匹配一個請求的 IP 地址或一個地址的網絡掩碼

access="hasIpAddress('

162.79.8.30')"

access="hasIpAddress('

162.0.0.0/224')"

hasRole(role)

No

用於匹配一個使用GrantedAuthority 的角色(相似於RoleVoter )

access="hasRole('ROLE

USER')"

hasAnyRole(role)

No

用於匹配一個使用GrantedAuthority 的角色列表。用戶匹配其中的任何一個都可放行。

access="hasRole('ROLE_

USER','ROLE_ADMIN')"

除了以上表格中的方法,在 SpEL 表達式中還有一系列的方法能夠做爲屬性。它們不須要圓括號或方法參數。

屬性

Web only?

描述

例子

permitAll

No

任何用戶都可訪問

access="permitAll"

denyAll

NO

任何用戶均不可訪問

access="denyAll"

anonymous

NO

匿名用戶可訪問

access="anonymous"

authenticated

NO

檢查用戶是否定證過

access="authenticated"

rememberMe

No

檢查用戶是否經過remember me 功能認證的

access="rememberMe"

fullyAuthenticated

No

檢查用戶是否經過提供完整的憑證信息來認證的

access="fullyAuthenticated"

須要記住的是, voter 的實現類必須基於請求的上下文返回一個投票的結果(容許、拒絕或者棄權)。你可能會認爲hasRole 會返回一個 Boolean 值,實際上正是如此。基於 SpEL 的訪問控制聲明必須是返回 Boolean 類型的表達式。返回值爲 true 意味着投票器的結果是容許訪問, false 的結果意味着投票器拒絕訪問。

【若是一個表達式的值不是 Boolean 類型的,你將會獲得以下的一個異常信息:org.springframework.expression.spel.SpelException:

EL1001E:Type conversion problem, cannot convert from

class java.lang.Integer to java.lang.Boolean 】

另外,表達式不能返回一個棄權類型的結果,除非訪問控制聲明不是一個合法 SpEL 表達式,在這種狀況下投票器將會放棄投票。

若是你不在意這些細小的約束, SpEL 訪問控制聲明可以提供一種靈活的配置訪問控制決策的方式。

 

在本章中,咱們提供了安全領域兩個重要概念即認證和受權的介紹。

 

在整體上了解咱們要進行安全保護的系統;

使用 Spring Security 的自動配置在三步以內實現了咱們應用的安全配置;

瞭解了在 Spring Security 中 servlet 過濾器的使用及重要性;

瞭解了認證和受權過程當中重要的角色,包括一些重要類實現的詳細介紹如 Authentication 和 UserDetails

體驗了與訪問控制規則有關的 SpEL 表達式的配置。

在接下來的一章中,咱們將經過添加一些加強用戶體驗的功能,把基於用戶名和密碼的認證提升一個新的水平。

相關文章
相關標籤/搜索