技術概述
1.1。運行環境
Spring Security 3.0須要Java 5.0運行時環境或更高版本。因爲Spring Security旨在以獨立的方式運行,所以無需將任何特殊配置文件放入Java運行時環境中。特別地,不須要配置特殊的Java認證和受權服務(JAAS)策略文件或將Spring Security放置到常見的類路徑位置中。html
一樣,若是您正在使用EJB容器或Servlet容器,則無需在任何位置放置任何特殊的配置文件,也不須要在服務器類加載器中包含Spring Security。全部必需的文件將包含在您的應用程序中。java
此設計提供最大的部署時間靈活性,由於您能夠簡單地將目標工件(不管是JAR,WAR仍是EAR)從一個系統複製到另外一個系統,並將當即起做用。web
1.2。核心組件
在Spring Security 3.0中,彈簧安全核心罐的內容最小化。它再也不包含與Web應用程序安全性,LDAP或命名空間配置相關的任何代碼。咱們將在這裏查看您在覈心模塊中找到的一些Java類型。它們表明了框架的構建塊,因此若是你須要超越一個簡單的命名空間配置,那麼重要的是你明白它們是什麼,即便你不須要直接與它們進行交互。算法
1.2.1。 SecurityContextHolder,SecurityContext和Authentication Objects
最基本的對象是SecurityContextHolder。這是咱們存儲應用程序當前安全上下文的詳細信息,其中包括當前正在使用應用程序的主體的詳細信息。默認狀況下,SecurityContextHolder使用ThreadLocal來存儲這些詳細信息,這意味着安全上下文老是可用於同一執行線程中的方法,即便安全上下文未做爲參數顯式傳遞給這些方法。這樣使用ThreadLocal是很是安全的,若是在處理當前主體的請求以後注意清除線程。固然,Spring Security會自動處理這個,因此沒有必要擔憂。spring
某些應用程序不徹底適用於使用ThreadLocal,由於它們與線程配合使用的具體方式。例如,Swing客戶端可能但願Java虛擬機中的全部線程都使用相同的安全上下文。 SecurityContextHolder能夠經過啓動策略進行配置,以指定如何存儲上下文。對於獨立應用程序,您將使用SecurityContextHolder.MODE_GLOBAL策略。其餘應用程序可能但願由安全線程產生的線程也承擔相同的安全身份。這是經過使用SecurityContextHolder.MODE_INHERITABLETHREADLOCAL實現的。您能夠經過兩種方式從默認SecurityContextHolder.MODE_THREADLOCAL更改模式。一是設置系統屬性,二是調用SecurityContextHolder上的靜態方法。大多數應用程序不須要從默認更改,但若是這樣作,請查看SecurityContextHolder的JavaDocs瞭解更多信息。sql
獲取有關當前用戶的信息數據庫
在SecurityContextHolder中,咱們存儲當前正在與應用程序交互的主體的詳細信息。 Spring Security使用Authentication對象來表示此信息。您一般不須要本身建立一個Authentication對象,但用戶查詢Authentication對象是至關廣泛的。您可使用如下代碼塊(從應用程序的任何位置)獲取當前驗證的用戶的名稱,例如:編程
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal(); if (principal instanceof UserDetails) { String username = ((UserDetails)principal).getUsername(); } else { String username = principal.toString(); }
調用getContext()返回的對象是SecurityContext接口的一個實例。這是保存在線程本地存儲中的對象。正如咱們將在下面看到的,使用Spring Security的大多數身份驗證機制將返回UserDetails的實例做爲主體。數組
1.2.2。 UserDetailsService
從上述代碼片斷中要注意的另外一個項目是能夠從Authentication對象獲取主體。校長只是一個對象。大部分時間能夠轉換成UserDetails對象。 UserDetails是Spring Security中的核心界面。它表明一個委託人,可是在可擴展和應用程序特定的方式。將UserDetails視爲您本身的用戶數據庫之間的適配器,以及SecurityContextHolder中須要的Spring Security。做爲一個表明你本身的用戶數據庫的東西,不少時候你會將UserDetails轉換成你應用程序提供的原始對象,因此你能夠調用業務特定的方法(如`getEmail(),'getEmployeeNumber()等等) 。瀏覽器
如今你可能想知道,因此我何時提供一個UserDetails對象?我怎麼作?我覺得你說這個東西是聲明性的,我不須要編寫任何Java代碼 - 給出了什麼?簡單的答案是有一個名爲UserDetailsService的特殊界面。此接口上惟一的方法接受基於String的用戶名參數,並返回UserDetails:
UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
成功認證後,UserDetails用於構建存儲在SecurityContextHolder中的Authentication對象(更多的內容)。 好消息是,咱們提供了一些UserDetailsService實現,包括使用內存映射(InMemoryDaoImpl)和另外一個使用JDBC(JdbcDaoImpl)的實現。 大多數用戶傾向於本身編寫,可是實現方式每每只是位於現有的數據訪問對象(DAO)之上,它表明了他們的員工,客戶或其餘應用程序的用戶。 記住,不管您的UserDetailsService返回的老是可使用上述代碼片斷從SecurityContextHolder獲取的優勢。
這每每是對UserDetailsService有些混淆。 它純粹是用戶數據的DAO,不執行其餘功能,而不是將數據提供給框架內的其餘組件。 特別地,它不認證用戶,這是由AuthenticationManager完成的。 在許多狀況下,若是須要自定義身份驗證過程,則直接 implement AuthenticationProvider更有意義。
1.2.3。一個GrantedAuthority
除了主體外,Authentication提供的另外一個重要方法是getAuthorities()。此方法提供了一個GrantedAuthority對象的數組。授予的受權是絕不奇怪的,授予委託人的受權。這些權限一般是「角色」,例如ROLE_ADMINISTRATOR或ROLE_HR_SUPERVISOR。這些角色後來配置爲Web受權,方法受權和域對象受權。 Spring Security的其餘部分可以解釋這些權力機構,並指望他們可以出席。 GrantedAuthority對象一般由UserDetailsService加載。
一般GrantedAuthority對象是應用程序範圍的權限。它們不是特定於給定的域對象。所以,您不可能有GrantedAuthority表示對Employee對象編號54的權限,由於若是有成千上萬的此類權限,您將很快用盡內存(或至少致使應用程序花費很長時間驗證用戶的時間)。固然,Spring Security也是爲了處理這個共同的要求而設計的,可是你也可使用項目的域對象安全功能。
1.2.4。概要
只是總結一下,到目前爲止咱們看到的Spring Security的主要組成部分是:
- SecurityContextHolder,提供對SecurityContext的訪問。
- SecurityContext,用於保存身份驗證和可能的特定於請求的安全信息。
- Authentication,以Spring Security特定的方式表示主體。
- GrantedAuthority,以反映授予主體的應用程序範圍的權限。
- UserDetails,提供從應用程序的DAO或其餘安全數據源構建Authentication對象所需的信息。
- UserDetailsService,在基於字符串的用戶名(或證書ID等)中傳遞時建立UserDetails。
如今,您已經瞭解了這些重複使用的組件,咱們來仔細看看認證過程。
1.3。認證
Spring Security能夠參與許多不一樣的認證環境。雖然咱們建議人們使用Spring Security進行身份驗證,而不是與現有的容器管理身份驗證集成,但它仍然受到支持 - 與您本身的專有身份驗證系統集成在一塊兒。
1.3.1。什麼是Spring Security中的身份驗證?
讓咱們考慮每一個人都熟悉的標準認證場景。
- 提示用戶使用用戶名和密碼登陸。
- 系統(成功)驗證用戶名的密碼是否正確。
- 得到該用戶的上下文信息(其角色列表等)。
- 爲用戶創建安全上下文
- 用戶繼續進行,潛在地執行可能由訪問控制機制保護的一些操做,該訪問控制機制根據當前的安全上下文信息檢查針對該操做的所需權限。
前三個項目構成了身份驗證過程,所以咱們將看看Spring Security中如何發生這種狀況。
- 獲取用戶名和密碼並將其組合爲UsernamePasswordAuthenticationToken(咱們前面看到的Authentication接口的一個實例)的一個實例。
- 令牌被傳遞給AuthenticationManager的實例以進行驗證。
- AuthenticationManager在成功認證時返回徹底填充的Authentication實例。
- 安全上下文經過調用SecurityContextHolder.getContext()。setAuthentication(...),傳入返回的認證對象來創建。
從那時起,用戶被認爲是認證的。咱們來看一些代碼做爲例子。
import org.springframework.security.authentication.*; import org.springframework.security.core.*; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.context.SecurityContextHolder; public class AuthenticationExample { private static AuthenticationManager am = new SampleAuthenticationManager(); public static void main(String[] args) throws Exception { BufferedReader in = new BufferedReader(new InputStreamReader(System.in)); while(true) { System.out.println("Please enter your username:"); String name = in.readLine(); System.out.println("Please enter your password:"); String password = in.readLine(); try { Authentication request = new UsernamePasswordAuthenticationToken(name, password); Authentication result = am.authenticate(request); SecurityContextHolder.getContext().setAuthentication(result); break; } catch(AuthenticationException e) { System.out.println("Authentication failed: " + e.getMessage()); } } System.out.println("Successfully authenticated. Security context contains: " + SecurityContextHolder.getContext().getAuthentication()); } } class SampleAuthenticationManager implements AuthenticationManager { static final List<GrantedAuthority> AUTHORITIES = new ArrayList<GrantedAuthority>(); static { AUTHORITIES.add(new SimpleGrantedAuthority("ROLE_USER")); } public Authentication authenticate(Authentication auth) throws AuthenticationException { if (auth.getName().equals(auth.getCredentials())) { return new UsernamePasswordAuthenticationToken(auth.getName(), auth.getCredentials(), AUTHORITIES); } throw new BadCredentialsException("Bad Credentials"); } }
這裏咱們寫了一個程序,要求用戶輸入用戶名和密碼,並執行上述順序。 咱們在這裏實現的AuthenticationManager將驗證其用戶名和密碼相同的用戶。 它爲每一個用戶分配一個角色。 上面的輸出將是:
Please enter your username: bob Please enter your password: password Authentication failed: Bad Credentials Please enter your username: bob Please enter your password: bob Successfully authenticated. Security context contains: \ org.springframework.security.authentication.UsernamePasswordAuthenticationToken@441d0230: \ Principal: bob; Password: [PROTECTED]; \ Authenticated: true; Details: null; \ Granted Authorities: ROLE_USER
請注意,您一般不須要編寫這樣的代碼。該過程一般在內部進行,例如在Web認證過濾器中。咱們剛剛將代碼放在這裏,以代表Spring Security中實際構成身份驗證的問題有一個簡單的答案。當SecurityContextHolder包含徹底填充的Authentication對象時,用戶將進行身份驗證。
1.3.2。直接設置SecurityContextHolder內容
事實上,Spring Security不介意如何將Authentication對象放在SecurityContextHolder中。惟一的關鍵要求是SecurityContextHolder包含一個Authentication,它表明AbstractSecurityInterceptor以前的一個主體(咱們將在後面看到更多信息)須要受權用戶操做。
您能夠(而且許多用戶)編寫本身的過濾器或MVC控制器,以提供與基於Spring Security的身份驗證系統的互操做性。例如,您可能正在使用容器管理身份驗證,這使得當前用戶能夠從ThreadLocal或JNDI位置得到。或者您能夠爲擁有傳統專有認證系統的公司工做,這是一種企業「標準」,您幾乎沒法控制。在這樣的狀況下,很容易讓Spring Security工做,而且仍然提供受權功能。全部您須要作的是編寫從位置讀取第三方用戶信息的過濾器(或等同物),構建一個特定於Spring Security的Authentication對象,並將其放入SecurityContextHolder中。在這種狀況下,您還須要考慮一般由內置身份驗證基礎設施自動處理的內容。例如,在將響應寫入客戶端以前,您可能須要先佔用建立一個HTTP會話來緩存請求之間的上下文。
若是您想知道AuthenticationManager是如何在現實世界中實現的,那麼咱們將在核心服務章節中看一下。
1.4。 Web應用程序中的身份驗證
如今讓咱們來探討在Web應用程序中使用Spring Security的狀況(不啓用web.xml安全性)。用戶如何認證和安全上下文創建?
考慮一個典型的Web應用程序的認證過程:
- 您訪問主頁,而後單擊連接。
- 請求發送到服務器,而且服務器決定您已請求受保護的資源。
- 因爲您目前沒有身份驗證,服務器會發回一個代表您必須驗證的響應。響應將是HTTP響應代碼或重定向到特定網頁。
- 根據身份驗證機制,您的瀏覽器將重定向到特定網頁,以便您填寫表單,或者瀏覽器將以某種方式檢索您的身份(經過BASIC身份驗證對話框,Cookie,X.509證書等) )。
- 瀏覽器將向服務器發送迴應。這將是一個包含您填寫的表單內容的HTTP POST,或包含您的身份驗證詳細信息的HTTP頭。
- 接下來,服務器將決定所提供的憑證是否有效。若是它們有效,下一步就會發生。若是它們無效,一般您的瀏覽器將被要求再次嘗試(因此您返回到上面的第二步)。
- 將重試您致使身份驗證過程的原始請求。但願您已經過足夠受權的權限驗證訪問受保護的資源。若是您有足夠的訪問權限,則請求將成功。不然,您將收到一個HTTP錯誤代碼403,這意味着「禁止」。
Spring Security具備不一樣的類,負責上述大部分步驟。主要參與者(使用它們的順序)是ExceptionTranslationFilter,AuthenticationEntryPoint和「認證機制」,它負責調用咱們上一節中看到的AuthenticationManager。
1.4.1。ExceptionTranslationFilter
ExceptionTranslationFilter是一個Spring Security過濾器,負責檢測拋出的任何Spring Security異常。這種異常一般將由受權服務的主要提供者AbstractSecurityInterceptor拋出。咱們將在下一節中討論AbstractSecurityInterceptor,可是如今咱們只須要知道它會生成Java異常,而且什麼也不知道HTTP或者如何去認證一個主體。相反,ExceptionTranslationFilter提供此服務,具體負責返回錯誤代碼403(若是主體已經經過身份驗證,所以徹底缺少足夠的訪問權限,如上面的第七步),或啓動AuthenticationEntryPoint(若是主體未被驗證,因此咱們須要開始步驟三)。
1.4.3。認證機制
一旦您的瀏覽器提交您的身份驗證憑據(做爲HTTP表單或HTTP標頭),服務器上須要「收集」這些身份驗證詳細信息。到目前爲止,咱們在上面列出的第六步。在Spring Security中,咱們有一個特殊名稱,用於從用戶代理(一般是Web瀏覽器)收集認證詳細信息,將其稱爲「身份驗證機制」。示例是表單登陸和基自己份驗證。一旦從用戶代理收集了認證詳細信息,就構建了一個認證「請求」對象,而後呈現給AuthenticationManager。
認證機制接收到徹底填充的認證對象後,會認爲該請求有效,將認證放入SecurityContextHolder,並引發原始請求重試(上述第七步)。另外一方面,若是AuthenticationManager拒絕了請求,則認證機制將要求用戶代理重試(第二步)。
1.4.4。在請求之間存儲SecurityContext
根據應用程序的類型,可能須要有一個策略來存儲用戶操做之間的安全上下文。在典型的Web應用程序中,用戶登陸一次,隨後由其會話ID標識。服務器緩存持續時間會話的主體信息。在Spring Security中,將SecurityContext存儲在請求之間的責任歸結於SecurityContextPersistenceFilter,默認狀況下,它將上下文做爲HTTP請求之間的HttpSession屬性存儲。它將每一個請求的上下文恢復到SecurityContextHolder,而且相當重要的是,在請求完成時清除SecurityContextHolder。爲了安全起見,您不該該直接與HttpSession交互。這樣作根本沒有理由 - 老是使用SecurityContextHolder。
許多其餘類型的應用程序(例如,無狀態RESTful Web服務)不使用HTTP會話,並將在每一個請求上從新進行身份驗證。可是,SecurityContextPersistenceFilter包含在鏈中仍然很重要,以確保每一個請求後SecurityContextHolder被清除。
在單個會話中接收併發請求的應用程序中,相同的SecurityContext實例將在線程之間共享。 即便正在使用ThreadLocal,它是從HttpSession爲每一個線程檢索的實例。 若是您但願臨時更改線程正在運行的上下文,則會產生影響。 若是您只是使用SecurityContextHolder.getContext(),並在返回的上下文對象上調用setAuthentication(anAuthentication),則認證對象將在共享相同SecurityContext實例的全部併發線程中更改。 您能夠自定義SecurityContextPersistenceFilter的行爲,爲每一個請求建立一個全新的SecurityContext,從而防止一個線程中的更改影響另外一個線程。 或者,您能夠在臨時更改上下文的位置建立一個新的實例。 方法SecurityContextHolder.createEmptyContext()老是返回一個新的上下文實例。
1.5。 Spring Security中的訪問控制(受權)
負責在Spring Security中進行訪問控制決策的主要接口是AccessDecisionManager。它具備一個決定方法,該方法採用表示主要請求訪問的Authentication對象,「安全對象」(見下文)和適用於對象的安全元數據屬性列表(例如訪問所需的角色列表被授予)。
1.5.1。安全和AOP建議
若是您熟悉AOP,您會發現有不一樣類型的建議可供選擇:以前,以後,投擲和周圍。周圍的建議是很是有用的,由於顧問能夠選擇是否繼續進行方法調用,不管是否修改響應,以及是否拋出異常。 Spring Security提供了方法調用以及Web請求的各類建議。咱們使用Spring的標準AOP支持來實現方法調用的各類建議,咱們使用標準過濾器來實現對Web請求的各類建議。
對於不熟悉AOP的人員,要了解的重點是Spring Security能夠幫助您保護方法調用以及Web請求。大多數人都有興趣在其服務層上確保方法調用。這是由於服務層是大多數業務邏輯駐留在當前一代Java EE應用程序中的地方。若是您只須要在服務層中保護方法調用,Spring的標準AOP就足夠了。若是您須要直接安全域對象,您可能會發現AspectJ值得考慮。
您能夠選擇使用AspectJ或Spring AOP執行方法受權,也能夠選擇使用過濾器執行Web請求受權。您能夠一塊兒使用這些方法中的零,一,二或三。主流使用模式是執行一些Web請求受權,再加上服務層上的一些Spring AOP方法調用受權。
1.5.2。安全對象和AbstractSecurityInterceptor
那麼什麼是「安全對象」呢? Spring Security使用該術語來引用能夠具備應用於其的安全性(例如受權決策)的任何對象。最多見的示例是方法調用和Web請求。
每一個受支持的安全對象類型都有本身的攔截器類,它是AbstractSecurityInterceptor的子類。重要的是,在調用AbstractSecurityInterceptor時,若是主體已通過身份驗證,SecurityContextHolder將包含有效的驗證。
AbstractSecurityInterceptor爲處理安全對象請求提供了一致的工做流,一般爲
- 查找與本請求相關聯的「配置屬性」
- 將安全對象,當前身份驗證和配置屬性提交給AccessDecisionManager進行受權決定
- 可選地,更改發起調用的身份驗證
- 容許安全對象調用繼續(假設訪問被授予)
- 一旦調用返回,調用AfterInvocationManager。若是調用引起異常,則不會調用AfterInvocationManager。
什麼是配置屬性?
「配置屬性」能夠被認爲是對AbstractSecurityInterceptor使用的類具備特殊含義的String。它們由框架內的接口ConfigAttribute表示。它們多是簡單的角色名稱或具備更復雜的含義,這取決於AccessDecisionManager實現的複雜程度。 AbstractSecurityInterceptor配置有一個SecurityMetadataSource,它用於查找安全對象的屬性。一般這個配置將被用戶隱藏。配置屬性將做爲安全方法上的註釋或做爲受保護URL上的訪問屬性輸入。例如,當咱們在命名空間介紹中看到相似<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一般只會繼續執行該請求。話雖如此,在極少數狀況下,用戶可能但願使用由AccessDecisionManager調用RunAsManager處理的不一樣身份驗證來替換SecurityContext內的Authentication。這在至關不尋常的狀況下多是有用的,例如,若是服務層方法須要調用遠程系統並呈現不一樣的身份。由於Spring Security自動將安全身份從一個服務器傳播到另外一個服務器(假設您正在使用正確配置的RMI或HttpInvoker遠程處理協議客戶端),這可能頗有用。
AfterInvocationManager
在安全對象調用進行而後返回 - 這可能意味着方法調用完成或過濾器鏈進行 - AbstractSecurityInterceptor得到處理調用的最後機會。在這個階段,AbstractSecurityInterceptor有可能修改返回對象。咱們可能但願發生這種狀況,由於在安全對象調用中沒法「受權」決定。因爲高度可插拔,AbstractSecurityInterceptor會將控件傳遞給AfterInvocationManager,以便在須要時實際修改該對象。這個類甚至能夠徹底替換對象,也能夠拋出異常,或者不以任何方式更改它。後調用檢查只有在調用成功時纔會執行。若是發生異常,將跳過附加檢查。
AbstractSecurityInterceptor及其相關對象顯示在安全攔截器和「安全對象」模型中
圖1.安全攔截器和「安全對象」模型
擴展安全對象模型
只有考慮採用全新方式攔截和受權請求的開發人員才須要直接使用安全對象。例如,能夠構建一個新的安全對象來保護對消息系統的調用。任何須要安全性的東西,也提供了一種截取通話語法的方式(如圍繞通知語義的AOP),能夠被作成一個安全的對象。話雖如此,大多數Spring應用程序將簡單地使用三個當前支持的安全對象類型(AOP Alliance MethodInvocation,AspectJ JoinPoint和Web請求FilterInvocation),具備完整的透明度。
1.6。本土化
Spring Security支持最終用戶可能看到的異常消息的本地化。若是您的應用程序是爲英語用戶設計的,那麼您無需執行任何操做,所以默認狀況下全部安全安全消息均爲英文。若是您須要支持其餘語言環境,則您須要知道的一切都包含在本節中。
全部異常消息均可以進行本地化,包括與認證失敗相關的消息和拒絕訪問(受權失敗)。專一於開發人員或系統部署者的異常和日誌記錄消息(包括不正確的屬性,違規接口違規,使用不正確的構造函數,啓動時間驗證,調試級日誌記錄)不會本地化,而是在Spring Security的代碼中用英文硬編碼。
在spring-security-core-xx.jar中發送,您將找到一個org.springframework.security包,該包又包含一個messages.properties文件,以及一些經常使用語言的本地化版本。這應該由你的`ApplicationContext`引用,由於Spring Security類實現了Spring的MessageSourceAware接口,並指望消息解析器在應用程序上下文啓動時被注入。一般全部您須要作的是在應用程序上下文中註冊一個bean來引用消息。一個例子以下所示:
<bean id="messageSource" class="org.springframework.context.support.ReloadableResourceBundleMessageSource"> <property name="basename" value="classpath:org/springframework/security/messages"/> </bean>
messages.properties根據標準資源束命名,並表示Spring Security消息支持的默認語言。此默認文件爲英文。
若是您但願自定義messages.properties文件或支持其餘語言,則應該複製文件,並相應地重命名,並將其註冊到上述bean定義中。這個文件中沒有大量的消息密鑰,因此本地化不該該被認爲是主要的。若是您確實執行此文件的本地化,請考慮經過記錄JIRA任務並附加適當命名的本地化版本的messages.properties來與社區共享您的工做。
Spring Security依賴於Spring的本地化支持,以便實際查找相應的消息。爲了使其工做,您必須確保來自傳入請求的區域設置存儲在Spring的org.springframework.context.i18n.LocaleContextHolder中。 Spring MVC的DispatcherServlet自動爲您的應用程序執行此操做,可是因爲Spring Security的過濾器在此以前被調用,所以LocaleContextHolder須要設置爲在調用過濾器以前包含正確的區域設置。你能夠本身作一個過濾器(它必須來自web.xml中的Spring Security過濾器),或者你可使用Spring的RequestContextFilter。有關使用Spring進行本地化的更多詳細信息,請參閱Spring Framework文檔。
「contacts」示例應用程序設置爲使用本地化消息。
2. 核心服務
如今咱們對Spring Security架構及其核心類進行了高級的概述,咱們來仔細研究一兩個核心接口及其實現,特別是AuthenticationManager,UserDetailsService和AccessDecisionManager。這些文件的其他部分會按期出現,所以重要的是您知道它們的配置及其操做方式。
2.1。 AuthenticationManager,ProviderManager和AuthenticationProvider
AuthenticationManager只是一個接口,因此實現能夠是咱們選擇的任何東西,但它在實踐中如何工做?若是咱們須要檢查多個身份驗證數據庫或不一樣身份驗證服務(如數據庫和LDAP服務器)的組合,該怎麼辦?
Spring Security中的默認實現被稱爲ProviderManager,而不是處理身份驗證請求自己,它將委託給已配置的AuthenticationProvider的列表,每一個都會依次查詢,以查看是否能夠執行身份驗證。每一個提供者將拋出異常或返回徹底填充的Authentication對象。記住咱們的好朋友,UserDetails和UserDetailsService?若是沒有,回到上一章,刷新你的記憶。驗證身份驗證請求的最多見方法是加載相應的UserDetails,並根據用戶輸入的密碼檢查加載的密碼。這是DaoAuthenticationProvider使用的方法(見下文)。當構建徹底填充的Authentication對象(從成功的認證返回並存儲在SecurityContext中)時,將使用加載的UserDetails對象(特別是包含的GrantedAuthority)。
若是您使用命名空間,則會在內部建立和維護ProviderManager實例,並經過使用命名空間認證提供程序元素(請參閱命名空間章節)向其添加提供程序。在這種狀況下,您不該該在應用程序上下文中聲明一個ProviderManager bean。可是,若是您不使用命名空間,則能夠這樣聲明:
<bean id="authenticationManager" class="org.springframework.security.authentication.ProviderManager"> <property name="providers"> <list> <ref local="daoAuthenticationProvider"/> <ref local="anonymousAuthenticationProvider"/> <ref local="ldapAuthenticationProvider"/> </list> </property> </bean>
在上面的例子中,咱們有三個提供者。它們按照顯示的順序進行嘗試(這經過使用列表來表示),每一個提供者均可以嘗試身份驗證,或者經過簡單地返回null來跳過身份驗證。若是全部的實現都返回null,那麼ProviderManager將拋出一個ProviderNotFoundException。若是您有興趣瞭解有關連接提供程序的更多信息,請參閱ProviderManager JavaDocs。
註冊Web表單登陸處理過濾器之類的身份驗證機制引用了ProviderManager,並將其稱之爲處理其身份驗證請求。您須要的提供商有時能夠與身份驗證機制互換,而在其餘時間,它們將取決於特定的身份驗證機制。例如,DaoAuthenticationProvider和LdapAuthenticationProvider與提交簡單的用戶名/密碼認證請求的任何機制兼容,所以可使用基於表單的登陸或HTTP基自己份驗證。另外一方面,一些認證機制建立一個認證請求對象,該對象只能由一種類型的AuthenticationProvider來解釋。一個例子是JA-SIG CAS,它使用服務票據的概念,所以只能由一個CasAuthenticationProvider進行身份驗證。您沒必要太在乎這一點,由於若是您忘記註冊一個合適的提供程序,則當嘗試進行身份驗證時,您將收到一個ProviderNotFoundException。
2.1.1。Erasing Credentials on Successful Authentication
默認狀況下(從Spring Security 3.1起),ProviderManager將嘗試從認證對象中清除成功認證請求返回的任何敏感憑證信息。這樣能夠防止密碼保留的時間長於必要的信息。
當您使用用戶對象緩存時,可能會致使問題,例如提升無狀態應用程序的性能。若是身份驗證包含對緩存中對象的引用(例如UserDetails實例),並刪除其憑據,則將沒法再對緩存的值進行身份驗證。若是您正在使用緩存,則須要考慮這一點。一個明顯的解決方案是首先建立對象的副本,不管是在緩存實現中仍是在建立返回的Authentication對象的AuthenticationProvider中。或者,您能夠禁用ProviderManager上的eraseCredentialsAfterAuthentication屬性。有關更多信息,請參閱Javadoc。
2.1.2。DaoAuthenticationProvider
Spring Security實現的最簡單的AuthenticationProvider是DaoAuthenticationProvider,它也是框架最先支持的。它利用UserDetailsService(做爲DAO)來查找用戶名,密碼和GrantedAuthority。它只需經過將UsernamePasswordAuthenticationToken中提交的密碼與UserDetailsService加載的密碼進行比較便可對用戶進行身份驗證。配置提供程序很簡單:
<bean id="daoAuthenticationProvider" class="org.springframework.security.authentication.dao.DaoAuthenticationProvider"> <property name="userDetailsService" ref="inMemoryDaoImpl"/> <property name="passwordEncoder" ref="passwordEncoder"/> </bean>
PasswordEncoder是可選的。 PasswordEncoder提供從配置的UserDetailsService返回的UserDetails對象中顯示的密碼的編碼和解碼。 這將在下面更詳細地討論。
2.2。 UserDetailsService實現
如本參考指南中的前面提到的,大多數認證提供商利用UserDetails和UserDetailsService接口。 回想一下,UserDetailsService的合同是一種單一的方法:
UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
返回的UserDetails是一個接口,它提供了容許非空提供驗證信息(如用戶名,密碼,受權的權限)以及用戶賬戶是啓用仍是禁用的getter。大多數身份驗證提供程序將使用「UserDetailsService」,即便用戶名和密碼實際上不被用做身份驗證決策的一部分。因爲某些其餘系統(如LDAP或X.509或CAS等)承擔了實際驗證憑據的責任,他們可能會將其返回的UserDetails對象用於其GrantedAuthority信息。
給定UserDetailsService是很是簡單的實現,用戶可使用他們選擇的持久性策略來檢索身份驗證信息很容易。話雖如此,Spring Security確實包含了一些有用的基礎實現,下面咱們來看一下。
2.2.1。內存中認證
易於使用建立一個自定義的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]
For example
jimi=jimispassword,ROLE_USER,ROLE_ADMIN,enabled bob=bobspassword,ROLE_USER,enabled
2.2.2。 JdbcDaoImpl
Spring Security還包括能夠從JDBC數據源獲取認證信息的UserDetailsService。使用內部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配置同樣。
權限組
默認狀況下,JdbcDaoImpl加載單個用戶的權限,假設當局直接映射到用戶(參見數據庫模式附錄)。另外一種方法是將權限劃分爲組,並將組分配給用戶。有些人喜歡這種方式,是管理用戶權利的一種手段。有關如何啓用組受權的更多信息,請參閱JdbcDaoImpl Javadoc。組織架構也包含在附錄中。
2.3。PasswordEncoder
Spring Security的PasswordEncoder接口用於支持使用以某種方式在持久存儲中編碼的密碼。您不該該以純文本形式存儲密碼。請務必使用bcrypt等單向密碼散列算法,該算法使用內置的每一個存儲密碼不一樣的鹽值。不要使用簡單的哈希函數,如MD5或SHA,甚至是鹽化版本。 Bcrypt故意設計爲緩慢,並阻止離線密碼破解,而標準散列算法快速,能夠輕鬆地用於在定製硬件上並行測試數千個密碼。您可能認爲這並不適用於您,由於您的密碼數據庫是安全的,脫機攻擊並非風險。若是是這樣作,請進行一些研究,並閱讀全部以這種方式受到妥協的高調網站,併爲保護密碼不安全而被劫持。最好在安全的一面。使用org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder「是安全性的一個不錯的選擇,還有其餘經常使用編程語言中的兼容實現,所以它也是互操做性的好選擇。
若是您使用已經散佈密碼的遺留系統,那麼您將須要使用與當前算法相匹配的編碼器,至少直到您將用戶遷移到更安全的方案(一般這將涉及要求用戶設置一個新的密碼,由於哈希是不可逆轉的)。 Spring Security具備包含傳統密碼編碼實現的包,即org.springframework.security.authentication.encoding。 DaoAuthenticationProvider能夠註冊新的或舊的PasswordEncoder類型。
2.3.1。什麼是hash?
Password hashing不是Spring Security所特有的,但對於不熟悉概念的用戶來講,這是一個常見的混亂來源。散列(或摘要)算法是一種單向函數,它從一些輸入數據(如密碼)產生一段固定長度的輸出數據(散列)。例如,字符串「密碼」(十六進制)的MD5哈希值是 5f4dcc3b5aa765d61d8327deb882cf99
在一個意義上說,散列是「單向的」,即在給定散列值的狀況下得到原始輸入,或者甚至可能產生該散列值的任何可能的輸入是很是困難的(其實是不可能的)。此屬性使哈希值對於身份驗證很是有用。它們能夠存儲在您的用戶數據庫中,做爲純文本密碼的替代方法,即便這些值受到威脅,也不會當即顯示可用於登陸的密碼。請注意,這也意味着您沒法在編碼密碼後恢復密碼。
2.3.2。Adding Salt to a Hash
使用密碼哈希的一個潛在問題是,若是使用通用單詞做爲輸入,則相對容易獲得散列的單向屬性。人們傾向於選擇相似的密碼,從之前被黑客攻擊的網站上能夠在網上得到這些密碼的巨大字典。例如,若是您使用谷歌搜索哈希值5f4dcc3b5aa765d61d8327deb882cf99,您將很快找到原始單詞「密碼」。以相似的方式,攻擊者能夠從標準單詞列表中構建散列詞典,並使用它來查找原始密碼。幫助防止這種狀況的一種方法是具備適當強度的密碼策略,以防止使用經常使用單詞。另外一個是在計算哈希時使用「鹽」。這是在計算哈希以前與每一個用戶的一個額外的一串已知數據,與密碼相結合。理想狀況下,數據應儘量隨機,但實際上任何鹽值一般都優於無。使用鹽意味着攻擊者必須爲每一個鹽值構建一個單獨的哈希字典,使攻擊更加複雜(但不是不可能)。
Bcrypt在編碼時自動爲每一個密碼生成隨機鹽值,並以標準格式將其存儲在bcrypt字符串中。
處理鹽的傳統方法是將SaltSource注入到DaoAuthenticationProvider中,該方法將爲特定用戶獲取鹽值並將其傳遞給PasswordEncoder。 使用bcrypt意味着您沒必要擔憂鹽處理的細節(例如存儲值的位置),由於它們都在內部完成。 因此咱們強烈推薦你使用bcrypt,除非你已經有一個系統存在鹽分開。
2.3.3。Hashing and Authentication
當認證提供者(如Spring Security的DaoAuthenticationProvider)須要根據用戶的已知值檢查提交的認證請求中的密碼,而且以某種方式對存儲的密碼進行編碼時,必須使用徹底相同的方式對提交的值進行編碼算法。因爲Spring Security沒法控制持久值,所以您能夠檢查這些是否兼容。若是您在Spring Security中爲您的身份驗證配置添加了密碼哈希值,而且數據庫中包含明文密碼,則認證沒法成功。即便您知道您的數據庫使用MD5編碼密碼,例如,您的應用程序配置爲使用Spring Security的Md5PasswordEncoder,仍然有可能出現問題。數據庫可能具備編碼在Base 64中的密碼,例如編碼器使用十六進制字符串(默認值)時。或者,您的數據庫可能使用大寫,而編碼器的輸出爲小寫。確保您使用已知的密碼和鹽組合來編寫測試以檢查已配置密碼編碼器的輸出,並檢查它是否符合數據庫值,而後再進一步嘗試經過應用程序進行身份驗證。使用像bcrypt這樣的標準將避免這些問題。
若是要直接在Java中生成編碼密碼以存儲在用戶數據庫中,則能夠在PasswordEncoder上使用encode方法。