9、Spring Security 5.2.2文檔翻譯-架構和實現

一旦您熟悉了基於名稱空間配置的應用程序的設置和運行,您可能希望進一步瞭解框架在名稱空間背後的實際工作方式。與大多數軟件一樣,Spring Security具有某些在整個框架中通常使用的中心接口、類和概念抽象。在參考指南的這一部分中,我們將研究其中的一些,並瞭解它們如何協同工作以支持Spring Security中的身份驗證和訪問控制。

1、 技術概覽

1.1、運行時環境

Spring Security 5.2.2.RELESE 需要Java 8或更高的運行時環境。由於Spring Security旨在以自包含的方式進行操作,因此不需要將任何特殊的配置文件放入Java運行時環境中。特別是,不需要配置特殊的Java身份驗證和授權服務(JAAS)策略文件,也不需要將Spring Security放入公共類路徑位置。類似地,如果您使用EJB容器或Servlet容器,則不需要在任何地方放置任何特殊配置文件,也不需要在服務器類加載器中包含Spring安全性。所有需要的文件都將包含在您的應用程序中。這種設計提供了最大的部署時間靈活性,因爲您可以簡單地將目標工件(不管是JAR、WAR還是EAR)從一個系統複製到另一個系統,它將立即工作。

1.2、核心組件

從Spring Security 3.0開始,Spring-Security-core.jar的內容被簡化到最少。它不再包含任何與web應用程序安全性、LDAP或名稱空間配置相關的代碼。我們將在這裏看一看在覈心模塊中找到的一些Java類型。它們表示框架的構建塊,因此如果您需要超越簡單的名稱空間配置,那麼理解它們是什麼是很重要的,即使您實際上不需要直接與它們交互。

SecurityContextHolder, SecurityContext and Authentication Objects

最基本的對象是SecurityContextHolder。這裏存儲應用程序當前安全上下文的詳細信息,其中包括當前使用應用程序的被認證對象的詳細信息。默認情況下,SecurityContextHolder使用一個ThreadLocal來存儲這些細節,這意味着安全上下文始終對同一執行線程中的方法可用,即使安全上下文沒有作爲參數顯式地傳遞給這些方法。如果在當前被認證對象的請求被處理後清除線程,那麼以這種方式使用ThreadLocal是非常安全的。當然,Spring Security會自動爲您處理這些問題,所以不必擔心。

有些應用程序並不完全適合使用ThreadLocal,因爲它們使用線程的特定方式。例如,Swing客戶機可能希望Java虛擬機中的所有線程使用相同的安全上下文。SecurityContextHolder可以在啓動時配置一個策略來指定您希望如何存儲上下文。對於一個獨立的應用程序,您將使用SecurityContextHolder.MODE_GLOBAL策略。其他應用程序可能希望安全線程派生的線程也具有相同的安全標識。這是通過使用SecurityContextHolder.MODE_INHERITABLETHREADLOCAL來實現的。您可以以兩種方式改變默認的SecurityContextHolder.MODE_THREADLOCAL。第一個是設置一個系統屬性,第二個是調用SecurityContextHolder中的一個靜態方法。大多數應用程序不需要更改默認設置,但是如果需要更改,請查看JavaDoc 中 SecurityContextHolder以瞭解更多信息。

獲取當前用戶的信息

在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的實例作爲主體返回。

The UserDetailsService

從上面的代碼片段中需要注意的另一項是,您可以從Authentication對象獲得一個被認證對象。被認證對象只是一個對象。大多數情況下,這可以轉換爲UserDetails對象。

UserDetails是Spring Security中的一個核心接口。它表示一個被認證對象,但是是以一種可擴展的和特定於應用程序的方式。將UserDetails看作是您自己的用戶數據庫和SecurityContextHolder中的Spring安全性需求之間的適配器。作爲來自您自己的用戶數據庫的內容的表示,您經常會將UserDetails轉換爲應用程序提供的原始對象,因此您可以調用特定於業務的方法(如getEmail(),getEmployeeNumber()等)。

現在您可能想知道,我什麼時候提供UserDetails對象?怎麼做呢?我以爲你說這個東西是聲明性的,我不需要寫任何Java代碼,這是爲什麼呢?簡而言之,有一個稱爲UserDetailsService的特殊接口。此接口上的惟一方法接受基於字符串的username參數並返回UserDetails。

UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;

這是Spring Security中爲用戶加載信息的最常用方法,只要需要用戶的信息,您就可以在整個框架中看到它的使用。

成功驗證後,UserDetails用於構建存儲在SecurityContextHolder中驗證對象(更多信息見下面)。好消息是,我們提供了許多UserDetailsService實現,其中一個使用內存映射(InMemoryDaoImpl),另一個使用JDBC (JdbcDaoImpl)。但是,大多數用戶都傾向於編寫自己的實現,他們的實現通常只是位於表示其僱員、客戶或應用程序的其他用戶的現有數據訪問對象(DAO)之上。請記住,無論您的UserDetailsService返回什麼,都可以使用上面的代碼片段從securitycontext中獲得。

UserDetailsService經常會引起一些混淆。它純粹是一個用於用戶數據的DAO,除了向框架內的其他組件提供數據外,不執行任何其他功能。特別是,它不會對用戶進行身份驗證,這是由AuthenticationManager完成的。在許多情況下,如果需要自定義身份驗證過程,直接實現AuthenticationProvider更有意義。

GrantedAuthority

除了被認證對象之外,身份驗證提供的另一個重要方法是getAuthority()。此方法提供GrantedAuthority 對象數組。GrantedAuthority是授予被認證對象的權力,這並不奇怪。這些權限通常是“角色”,例如ROLE_ADMINISTRATOR或ROLE_HR_SUPERVISOR。稍後將爲web授權、方法授權和域對象授權配置這些角色。Spring Security的其他部分能夠解釋這些權限,並期待它們的出現。GrantedAuthority 通常由UserDetailsService加載。

通常,GrantedAuthority對象是應用程序範圍的權限。它們並不特定於給定的域對象。因此,您可能沒有GrantedAuthority來表示對Employee對象編號54的權限,因爲如果有成千上萬個這樣的權限,您將很快耗盡內存(或者,至少會導致應用程序需要很長時間來驗證用戶)。當然,Spring Security是專門爲處理這一常見需求而設計的,但是您應該使用項目的域對象安全功能來實現這一目的。

總結

簡單回顧一下,到目前爲止我們看到的Spring安全性的主要構建塊是:

  • SecurityContextHolder,提供對SecurityContext的訪問。
  • SecurityContext,保存Authentication和特定於請求的安全信息。
  • Authentication,特定的Spring Security的方式表示被認證對象。
  • GrantedAuthority,以反映授予被認證對象的應用程序範圍的權限。
  • UserDetails,提供從應用程序的DAOs或其他安全數據源構建身份驗證對象所需的信息。
  • UserDetailsService,在傳入基於字符串的用戶名(或證書ID等)時創建用戶詳細信息。

既然您已經對這些重複使用的組件有了一定的瞭解,現在讓我們進一步瞭解一下身份驗證的過程。

1.3、Authentication

Spring Security可以參與許多不同的身份驗證環境。雖然我們建議人們使用Spring Security進行身份驗證,而不與現有的容器管理身份驗證集成,但它仍然受到支持—就像與您自己的專有身份驗證系統集成一樣。

什麼是Spring安全中的身份驗證?

讓我們考慮一個大家都熟悉的標準身份驗證場景。

  1. 提示用戶使用用戶名和密碼登錄。
  2. 系統(成功地)驗證用戶名的密碼正確。
  3. 獲取該用戶的上下文信息(角色列表等)。
  4. 爲用戶建立安全上下文。
  5. 用戶繼續執行某些操作,這些操作可能受到訪問控制機制的保護,該機制根據當前安全上下文信息檢查操作所需的權限。

前四項構成了身份驗證過程,因此我們將瞭解這些內容在Spring Security中是如何發生的。

  1. 獲取用戶名和密碼並將其組合爲UsernamePasswordAuthenticationToken實例(Authentication接口的實例,如前所述)。
  2. 令牌被傳遞給AuthenticationManager實例進行驗證。
  3. 成功驗證後,AuthenticationManager返回一個完全填充的 Authentication實例。
  4. 通過調用securitycontext . getcontext (). setauthentication(…)來建立安全上下文,並傳入返回的authentication對象。
    從那時起,用戶就被認爲是經過身份驗證的。讓我們以一些代碼爲例。
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 對象時,將對用戶進行身份驗證。

直接設置SecurityContextHolder的內容

事實上,Spring Security並不介意您如何將Authentication對象放入SecurityContextHolder中。
惟一的關鍵要求是SecurityContextHolder包含一個Authentication,它表示AbstractSecurityInterceptor(稍後我們將詳細介紹)需要授權用戶操作之前的被認證對象。

您可以(許多用戶也這樣做)編寫自己的過濾器或MVC控制器,以提供與不基於Spring安全性的身份驗證系統的互操作性。例如,您可能正在使用容器管理的身份驗證,它使當前用戶可以從ThreadLocal或JNDI位置訪問。或者,您可能在一家擁有遺留的專有身份驗證系統的公司工作,這是一種您幾乎無法控制的企業“標準”。在這種情況下,很容易讓Spring Security正常工作,同時仍然提供授權功能。您所需要做的就是編寫一個過濾器(或等效的過濾器),從某個位置讀取第三方用戶信息,構建一個Spring特定於安全的身份驗證對象,並將其放入SecurityContextHolder中。在這種情況下,您還需要考慮通常由內置的身份驗證基礎結構自動處理的事情。例如,在將響應寫入客戶機之前,您可能需要預先創建一個HTTP會話來緩存請求之間的上下文。(一旦提交了響應,就不可能創建會話。)

如果您想知道如何在真實世界的示例中實現AuthenticationManager,我們將在覈心服務一章中介紹。

1.4 在web應用中認證

現在讓我們研究一下在web應用程序中使用Spring安全性的情況(沒有啓用web.xml安全性)。如何驗證用戶身份並建立安全上下文?

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

  1. 你訪問主頁,然後點擊一個鏈接。
  2. 一個請求被髮送到服務器,服務器決定您請求的是受保護的資源。
  3. 由於目前還沒有進行身份驗證,服務器將發回一個響應,指示您必須進行身份驗證。響應可以是HTTP響應代碼,也可以是指向特定web頁面的重定向。
  4. 根據身份驗證機制,您的瀏覽器將重定向到特定的web頁面,以便您填寫表單,或者瀏覽器將以某種方式檢索您的身份。(通過一個基本的認證對話框,一個cookie,一個X.509證書等。)
  5. 瀏覽器將向服務器發回響應。這將是一個包含您填寫的表單內容的HTTP POST,或者是一個包含您的身份驗證詳細信息的HTTP頭。
  6. 接下來,服務器將決定所提供的憑據是否有效。如果它們是有效的,下一步就會發生。如果它們是無效的,通常您的瀏覽器將被要求再次嘗試(因此您返回到上面的步驟2)。
  7. 接下來,服務器將決定所提供的憑據是否有效。如果它們是有效的,下一步就會發生。如果它們是無效的,通常您的瀏覽器將被要求再次嘗試(因此您返回到上面的步驟2)。

Spring Security有不同的類負責上述大多數步驟。主要的參與者(按使用的順序)是ExceptionTranslationFilter、一個AuthenticationEntryPoint和一個“身份驗證機制”,後者負責調用AuthenticationManager,我們在前面的小節中已經看到了。

ExceptionTranslationFilter

ExceptionTranslationFilter是一個Spring安全過濾器,它負責檢測拋出的任何Spring安全異常。此類異常通常由AbstractSecurityInterceptor拋出,它是授權服務的主要提供者。我們將在下一節中討論AbstractSecurityInterceptor,但是現在我們只需要知道它會產生Java異常,並且不知道HTTP或如何對被認證對象進行身份驗證。相反ExceptionTranslationFilter提供這種服務,具體負責返回錯誤代碼403(如果被認證對象已經過身份驗證的,因此只是缺乏足夠的訪問——按步驟7),或啓動一個AuthenticationEntryPoint(如果被認證對象還沒有經過身份驗證的,因此我們要開始第三步)。

AuthenticationEntryPoint

AuthenticationEntryPoint負責上述列表中的第三步。可以想象,每個web應用程序都有一個默認的身份驗證策略(可以像Spring Security中的幾乎所有東西一樣進行配置,但是現在讓我們保持簡單)。每個主要的身份驗證系統都有自己的AuthenticationEntryPoint實現,它通常執行步驟3中描述的操作之一。

Authentication Mechanism

一旦您的瀏覽器提交了您的身份驗證憑證(作爲HTTP表單post或HTTP頭文件),服務器上就需要有一些東西來“收集”這些身份驗證細節。到目前爲止,我們處於上述列表的第六步。在Spring Security中,我們爲從用戶代理(通常是web瀏覽器)收集身份驗證細節的功能提供了一個特殊的名稱,將其稱爲“身份驗證機制”。示例有基於表單的登錄和基本身份驗證。從用戶代理收集身份驗證細節之後,將構建Authentication“請求”對象,然後將其提交給AuthenticationManager。

在請求之間存儲SecurityContext

根據應用程序的類型,可能需要一種策略來存儲用戶操作之間的安全上下文。在典型的web應用程序中,用戶登錄一次,然後通過其會話Id進行標識。服務器在會話期間緩存被認證對象的信息。在Spring Security中,在請求之間存儲SecurityContext的責任落在SecurityContextPersistenceFilter上,它默認將上下文存儲爲HTTP請求之間的HttpSession屬性。它將每個請求的上下文恢復到SecurityContextHolder,並且,最重要的是,在請求完成時清除SecurityContextHolder。出於安全目的,您不應該直接與HttpSession交互,這樣做是沒有理由的,所以使用SecurityContextHolder來代替。

許多其他類型的應用程序(例如,無狀態rest式web服務)不使用HTTP會話,而是對每個請求重新進行身份驗證。但是,在鏈中包含SecurityContextPersistenceFilter仍然很重要,以確保在每個請求之後清除SecurityContextHolder。

在一個在單個會話中接收併發請求的應用程序中,同一個SecurityContext實例將在線程之間共享。即使使用了ThreadLocal,它也是每個線程從HttpSession檢索到的同一個實例。如果您希望臨時更改線程運行的上下文,這將產生影響。如果您只是使用SecurityContext.getcontext(),並在返回的上下文對象上調用setAuthentication(anAuthentication),那麼在所有共享同一個SecurityContext實例的併發線程中,Authentication對象都會發生變化。您可以自定義SecurityContextPersistenceFilter的行爲,爲每個請求創建一個全新的SecurityContext,從而防止一個線程中的更改影響到另一個線程。或者,您可以在臨時更改上下文的地方創建一個新實例。方法SecurityContext . createEmptyContext()總是返回一個新的上下文實例。

1.5 Spring Security中的訪問控制(授權)

在Spring Security中,負責訪問控制決策的主接口是AccessDecisionManager。它有一個decide方法,該方法接受一個表示請求訪問的被認證的Authentication對象、一個“secure object”(見下面)和一個應用於該對象的安全元數據屬性列表(如授予訪問所需的角色列表)。

Security and AOP Advice

如果熟悉AOP,就會知道有不同類型的advice可用:before、after、throw和around。around advice非常有用,因爲advisor工具可以選擇是否繼續方法調用,是否修改響應,是否拋出異常。Spring Security爲方法調用和web請求提供了一個around advice。我們使用Spring的標準AOP支持實現了方法調用的around advice,我們使用標準過濾器實現了web請求的around advice。

對於那些不熟悉AOP的人來說,要理解的關鍵點是Spring Security 可以幫助您保護方法調用和web請求。大多數人感興趣的是保護其服務層上的方法調用。這是因爲服務層是當前一代Java EE應用程序中大部分業務邏輯所在的地方。如果只需要在服務層中保護方法調用,Spring的標準AOP就足夠了。如果您需要直接保護域對象,您可能會發現AspectJ是值得考慮的。可以選擇使用AspectJ或Spring AOP執行方法授權,也可以選擇使用過濾器執行web請求授權。您可以同時使用這些方法中的零個、一個、兩個或三個。主流的使用模式是執行一些web請求授權,同時在服務層上執行一些Spring AOP方法調用授權。

Secure Objects and the AbstractSecurityInterceptor

那麼什麼是“secure object”呢?
Spring Security使用這個術語來指任何可以應用安全性(例如授權決策)的對象。最常見的例子是方法調用和web請求。

每種受支持的安全對象類型都有自己的攔截器類,它是AbstractSecurityInterceptor的子類。重要的是,在調用AbstractSecurityInterceptor時,如果被認證對象已經過身份驗證,SecurityContext將包含有效的Authentication。

AbstractSecurityInterceptor爲處理安全對象請求提供了一致的工作流,通常是:

  1. 查找與當前請求相關聯的“配置屬性”
  2. 爲授權決策向AccessDecisionManager提交安全對象、當前身份驗證和配置屬性。
  3. 可以選擇更改調用的Authentication
  4. 允許繼續進行安全對象調用(假設授予了訪問權)
  5. 一旦調用返回,則調用AfterInvocationManager(如果已配置)。如果調用引發異常,AfterInvocationManager將不會被調用。

什麼是配置屬性?

可以將“配置屬性”視爲對AbstractSecurityInterceptor使用的類具有特殊意義的字符串。它們由框架中的接口ConfigAttribute表示。它們可能是簡單的角色名,也可能有更復雜的含義,這取決於AccessDecisionManager實現的複雜程度。AbstractSecurityInterceptor配置了一個SecurityMetadataSource,它用來查找安全對象的屬性。通常這種配置對用戶是隱藏的。配置屬性將作爲安全方法上的註解或安全url上的訪問屬性輸入。例如,當我們在名稱空間介紹中看到類似<intercept-url pattern=’/secure/**’ access=‘ROLE_A,ROLE_B’/>這樣的內容時,這意味着配置屬性ROLE_A和ROLE_B適用於匹配給定模式的web請求。在實踐中,使用默認的AccessDecisionManager配置,這意味着任何具有匹配這兩個屬性之一的授權權限的人都將被允許訪問。嚴格地說,它們只是屬性,其解釋依賴於AccessDecisionManager實現。前綴ROLE_的使用表明這些屬性是角色,應該由Spring Security的RoleVoter使用。這僅在使用voter-based的AccessDecisionManager時相關。我們將在authorization chapter中看到AccessDecisionManager是如何實現的。

RunAsManager

假設AccessDecisionManager決定允許請求,AbstractSecurityInterceptor通常只處理請求。話雖如此,在極少數情況下,用戶可能希望用不同的Authentication替換SecurityContext中的Authentication,這是由調用RunAsManager的AccessDecisionManager處理的。這在相當不尋常的情況下可能有用,比如服務層方法需要調用遠程系統並提供不同的標識。因爲Spring Security會自動將安全標識從一個服務器傳播到另一個服務器(假設您使用的是正確配置的RMI或HttpInvoker remoting協議客戶機),所以這可能很有用。

AfterInvocationManager

在完成安全對象調用並返回之後(這可能意味着完成方法調用或執行過濾器鏈),AbstractSecurityInterceptor將獲得最後一次處理調用的機會。在這個階段,AbstractSecurityInterceptor可能對修改返回對象感興趣。我們可能希望發生這種情況,因爲無法“在進入”安全對象調用的過程中做出授權決策。由於可高度插拔,AbstractSecurityInterceptor將控制權傳遞給AfterInvocationManager,以便在需要時實際修改對象。這個類甚至可以完全替換對象,或者拋出異常,或者不根據自己的選擇以任何方式更改它。只有在調用成功時,纔會執行調用後檢查。如果發生異常,將跳過其他檢查。AbstractSecurityInterceptor及其相關對象如圖9.1所示
Figure 9.1. Security interceptors and the "secure object" model

擴展安全對象模型

只有考慮使用一種全新的攔截和授權請求的方法的開發人員才需要直接使用安全對象。例如,可以構建一個新的安全對象來保護對消息傳遞系統的調用。何需要安全性並提供攔截調用的方法(如圍around advice語義的AOP)的東西都可以成爲安全對象。儘管如此,大多數Spring應用程序將完全透明地使用當前支持的三種安全對象類型(AOP Alliance MethodInvocation、AspectJ JoinPoint和web請求FilterInvocation)。

2、核心服務

現在我們已經大致瞭解了Spring安全體系結構及其核心類,接下來讓我們進一步瞭解一兩個核心接口及其實現,特別是AuthenticationManager、UserDetailsService和AccessDecisionManager。這些內容經常出現在本文檔的其餘部分中,因此瞭解它們的配置和操作方式非常重要。

2.1、The AuthenticationManager, ProviderManager and AuthenticationProvider

AuthenticationManager只是一個接口,所以我們可以選擇任何實現,但它在實踐中是如何工作的呢?如果需要檢查多個身份驗證數據庫或不同身份驗證服務(如數據庫和LDAP服務器)的組合,該怎麼辦?

Spring Security中的默認實現稱爲ProviderManager,它不處理身份驗證請求本身,而是將其委託給一個已配置的AuthenticationProviders列表,依次查詢每個提供者,以查看是否可以執行身份驗證。每個提供者要麼拋出一個異常,要麼返回一個完全填充的Authentication對象。還記得我們的好朋友UserDetails和UserDetailsService嗎?如果不記得,回顧前一章。驗證身份驗證請求最常用的方法是加載相應的UserDetails並根據用戶輸入的密碼檢查已加載的密碼。這是DaoAuthenticationProvider使用的方法(參見下面)。加載的UserDetails對象——特別是它包含的GrantedAuthoritys——將在構建完全填充的Authentication對象時使用,該對象從成功的身份驗證返回並存儲在SecurityContext中。

如果使用名稱空間,將在內部創建和維護ProviderManager的實例,並通過使用名稱空間身份驗證提供者元素向其添加提供者(請參閱 the namespace chapter)。在這種情況下,不應該在應用程序上下文中聲明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>

在上面的例子中,我們有三個提供者。它們按照顯示的順序進行嘗試(這是使用列表所暗示的),每個提供者都可以嘗試身份驗證,或者通過簡單地返回null來跳過身份驗證。如果所有實現都返回null, ProviderManager將拋出ProviderNotFoundException。如果您有興趣瞭解關於鏈接提供程序的更多信息,請參考ProviderManager Javadoc。

身份驗證機制(如web表單登錄處理篩選器)被注入到ProviderManager的引用,並將調用它來處理身份驗證請求。您需要的提供者有時可以與身份驗證機制互換,而在其他時候則依賴於特定的身份驗證機制。例如,DaoAuthenticationProvider和LdapAuthenticationProvider與提交簡單用戶名/密碼身份驗證請求的任何機制都是兼容的,因此可以與基於表單的登錄或HTTP基本身份驗證工作。另一方面,一些身份驗證機制創建一個身份驗證請求對象,該對象只能由單一類型的AuthenticationProvider解釋。這方面的一個例子是JA-SIG CAS,它使用服務票據的概念,因此只能通過CasAuthenticationProvider進行身份驗證。您不必太關心這個問題,因爲如果忘記註冊合適的提供者,那麼在嘗試進行身份驗證時,您只會收到ProviderNotFoundException。

成功驗證時擦除憑據

默認情況下(從Spring Security 3.1開始),ProviderManager將嘗試從成功的身份驗證請求返回的Authentication對象中清除任何敏感的憑據信息。這可以防止像密碼這樣的信息被保留超過必要的時間。

這可能會在您使用用戶對象的緩存時造成問題,例如,在無狀態應用程序中提高性能。如果身份驗證包含對緩存中的對象的引用(例如UserDetails實例),並且該對象的憑據已被刪除,那麼將無法根據緩存的值進行身份驗證。如果使用緩存,則需要考慮這一點。一個明顯的解決方案是,首先在緩存實現中或在創建返回的Authentication對象的AuthenticationProvider中複製對象。或者,您可以在ProviderManager上禁用eraseCredentialsAfterAuthentication屬性。有關更多信息,請參見Javadoc。

DaoAuthenticationProvider

Spring Security實現的最簡單的AuthenticationProvider是DaoAuthenticationProvider,它也是該框架最早支持的一個。它利用UserDetailsService(作爲一個DAO)來查找用戶名、密碼和授權權限。它只需將UsernamePasswordAuthenticationToken中提交的密碼與UserDetailsService加載的密碼進行比較,就可以對用戶進行身份驗證。配置提供者非常簡單:

<bean id="daoAuthenticationProvider"
    class="org.springframework.security.authentication.dao.DaoAuthenticationProvider">
<property name="userDetailsService" ref="inMemoryDaoImpl"/>
<property name="passwordEncoder" ref="passwordEncoder"/>
</bean>

編碼器是可選的。PasswordEncoder提供密碼的編碼和解碼,這些密碼出現在從配置的UserDetailsService返回的UserDetails對象中。下面將對此進行更詳細的討論

2.2、 UserDetailsService實現

正如本參考指南前面所提到的,大多數身份驗證提供者都利用了UserDetails和UserDetailsService接口。回想一下UserDetailsService的契約是一個單一的方法:

UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;

返回的UserDetails是一個接口,它提供了一些getters,這些getters保證提供非空的身份驗證信息,比如用戶名、密碼、授予的權限以及用戶帳戶是否啓用或禁用。大多數身份驗證提供者將使用UserDetailsService,即使用戶名和密碼實際上並不是身份驗證決策的一部分。他們可能只使用返回的UserDetails對象的授權信息,因爲其他一些系統(如LDAP或X.509或CAS等)承擔了實際驗證憑證的責任。

由於UserDetailsService的實現非常簡單,用戶可以很容易地使用自己選擇的持久性策略來檢索身份驗證信息。儘管如此,Spring Security確實包含了一些有用的基本實現,我們將在下面介紹。

內存中的身份驗證

創建一個自定義UserDetailsService實現很容易使用,它可以從選擇的持久性引擎中提取信息,但是許多應用程序不需要這樣的複雜性。如果您正在構建一個原型應用程序,或者剛剛開始集成Spring Security,而又不想花時間配置數據庫或編寫UserDetailsService實現,那麼這一點尤其重要。對於這種情況,一個簡單的選擇是使用來自安全名稱空間的user-service元素:

<user-service id="userDetailsService">
<!-- Password is prefixed with {noop} to indicate to DelegatingPasswordEncoder that
NoOpPasswordEncoder should be used. This is not safe for production, but makes reading
in samples easier. Normally passwords should be hashed using BCrypt -->
<user name="jimi" password="{noop}jimispassword" authorities="ROLE_USER, ROLE_ADMIN" />
<user name="bob" password="{noop}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來使用不同的關係數據庫管理系統。與其他任何Spring配置一樣,您也可以使用從JNDI獲得的全局數據源。

Authority Groups

默認情況下,JdbcDaoImpl加載單個用戶的權限,並假設這些權限是直接映射到用戶組(請參閱database schema appendix)。另一種方法是將權限劃分爲組並將組分配給用戶。有些人喜歡將此方法作爲管理用戶權限的一種方法。有關如何啓用組權限的更多信息,請參閱JdbcDaoImpl Javadoc。附錄中還包含了組模式。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章