匿名驗證

總覽

一般而言,採用「預設拒絕」的立場被認為是良好的安全實務,您需明確指定允許的項目,並拒絕所有其他項目。定義未經驗證的使用者可以存取的內容情況類似,對於網路應用程式而言尤其如此。許多網站要求使用者必須通過驗證才能存取少數 URL 以外的任何內容 (例如首頁和登入頁面)。在這種情況下,為這些特定 URL 而非每個受保護的資源定義存取組態屬性最為容易。換句話說,有時最好預設要求 ROLE_SOMETHING,並僅允許此規則的某些例外情況,例如應用程式的登入、登出和首頁。您也可以完全從篩選器鏈中省略這些頁面,從而繞過存取控制檢查,但由於其他原因,這可能是不理想的,特別是當頁面對於已驗證的使用者行為不同時。

這就是我們所說的匿名驗證。請注意,「匿名驗證」使用者與未經驗證的使用者之間沒有真正的概念差異。Spring Security 的匿名驗證只是為您提供一種更方便的方式來組態您的存取控制屬性。即使 SecurityContextHolder 中實際上存在匿名驗證物件,對 servlet API 呼叫 (例如 getCallerPrincipal) 的呼叫仍會傳回 null。

在其他情況下,匿名驗證也很有用,例如當稽核攔截器查詢 SecurityContextHolder 以識別哪個主體負責給定操作時。如果類別知道 SecurityContextHolder 始終包含 Authentication 物件且永遠不包含 null,則可以更穩健地編寫類別。

組態

當您使用 HTTP 組態 (在 Spring Security 3.0 中引入) 時,會自動提供匿名驗證支援。您可以使用 <anonymous> 元素自訂 (或停用) 它。除非您使用傳統 Bean 組態,否則您不需要組態此處描述的 Bean。

三個類別協同工作以提供匿名驗證功能。AnonymousAuthenticationToken 是 Authentication 的實作,並儲存適用於匿名主體的 GrantedAuthority 實例。有一個對應的 AnonymousAuthenticationProvider,它被鏈結到 ProviderManager 中,以便接受 AnonymousAuthenticationToken 實例。最後,AnonymousAuthenticationFilter 在正常驗證機制之後鏈結,如果 SecurityContextHolder 中沒有現有的 Authentication,則會自動將 AnonymousAuthenticationToken 新增到 SecurityContextHolder 中。篩選器和驗證提供者定義如下

<bean id="anonymousAuthFilter"
	class="org.springframework.security.web.authentication.AnonymousAuthenticationFilter">
<property name="key" value="foobar"/>
<property name="userAttribute" value="anonymousUser,ROLE_ANONYMOUS"/>
</bean>

<bean id="anonymousAuthenticationProvider"
	class="org.springframework.security.authentication.AnonymousAuthenticationProvider">
<property name="key" value="foobar"/>
</bean>

金鑰在篩選器和驗證提供者之間共用,以便前者建立的 Token 可以被後者接受

此處金鑰屬性的使用不應被視為提供任何真正的安全性。它僅僅是一種簿記練習。如果您在驗證用戶端可以建構 Authentication 物件 (例如使用 RMI 呼叫) 的情況下,共用包含 AnonymousAuthenticationProvider 的 ProviderManager,則惡意用戶端可能會提交它自己建立的 AnonymousAuthenticationToken (使用選定的使用者名稱和授權清單)。如果金鑰是可猜測的或可以被發現的,則 Token 將被匿名提供者接受。這在正常使用情況下不是問題。但是,如果您使用 RMI,則應使用自訂的 ProviderManager,省略匿名提供者,而不是共用您用於 HTTP 驗證機制的 ProviderManager。

userAttribute 以 usernameInTheAuthenticationToken,grantedAuthority[,grantedAuthority] 的形式表示。在 InMemoryDaoImpl 的 userMap 屬性的等號後也使用相同的語法。

如先前所述,匿名驗證的好處是所有 URI 模式都可以應用安全性,如下例所示

<bean id="filterSecurityInterceptor"
	class="org.springframework.security.web.access.intercept.FilterSecurityInterceptor">
<property name="authenticationManager" ref="authenticationManager"/>
<property name="accessDecisionManager" ref="httpRequestAccessDecisionManager"/>
<property name="securityMetadata">
	<security:filter-security-metadata-source>
	<security:intercept-url pattern='/index.jsp' access='ROLE_ANONYMOUS,ROLE_USER'/>
	<security:intercept-url pattern='/hello.htm' access='ROLE_ANONYMOUS,ROLE_USER'/>
	<security:intercept-url pattern='/logoff.jsp' access='ROLE_ANONYMOUS,ROLE_USER'/>
	<security:intercept-url pattern='/login.jsp' access='ROLE_ANONYMOUS,ROLE_USER'/>
	<security:intercept-url pattern='/**' access='ROLE_USER'/>
	</security:filter-security-metadata-source>" +
</property>
</bean>

AuthenticationTrustResolver

完善匿名驗證討論的是 AuthenticationTrustResolver 介面及其對應的 AuthenticationTrustResolverImpl 實作。此介面提供 isAnonymous(Authentication) 方法,允許感興趣的類別考慮這種特殊類型的驗證狀態。ExceptionTranslationFilter 在處理 AccessDeniedException 實例時使用此介面。如果拋出 AccessDeniedException 且驗證類型為匿名類型,則篩選器不會拋出 403 (禁止) 回應,而是啟動 AuthenticationEntryPoint,以便主體可以正確驗證。這是必要的區別。否則,主體將始終被視為「已驗證」,並且永遠不會有機會通過表單、基本、摘要或其他一些正常驗證機制登入。

我們經常看到早期攔截器組態中的 ROLE_ANONYMOUS 屬性被 IS_AUTHENTICATED_ANONYMOUSLY 取代,這在定義存取控制時實際上是相同的。這是使用 AuthenticatedVoter 的一個範例,我們在授權章節中介紹了它。它使用 AuthenticationTrustResolver 來處理此特定組態屬性,並授予匿名使用者存取權。AuthenticatedVoter 方法更強大,因為它可讓您區分匿名使用者、記住我使用者和完全驗證的使用者。但是,如果您不需要此功能,則可以堅持使用 ROLE_ANONYMOUS,它由 Spring Security 的標準 RoleVoter 處理。

在 Spring MVC 中取得匿名驗證

這表示像這樣的結構

  • Java

  • Kotlin

@GetMapping("/")
public String method(Authentication authentication) {
	if (authentication instanceof AnonymousAuthenticationToken) {
		return "anonymous";
	} else {
		return "not anonymous";
	}
}
@GetMapping("/")
fun method(authentication: Authentication?): String {
    return if (authentication is AnonymousAuthenticationToken) {
        "anonymous"
    } else {
        "not anonymous"
    }
}

即使是匿名請求,也總是會傳回「not anonymous」。原因是 Spring MVC 使用 HttpServletRequest#getPrincipal 解析參數,當請求為匿名時,HttpServletRequest#getPrincipal 為 null。

如果您想在匿名請求中取得 Authentication,請改用 @CurrentSecurityContext

為匿名請求使用 CurrentSecurityContext
  • Java

  • Kotlin

@GetMapping("/")
public String method(@CurrentSecurityContext SecurityContext context) {
	return context.getAuthentication().getName();
}
@GetMapping("/")
fun method(@CurrentSecurityContext context : SecurityContext) : String =
		context!!.authentication!!.name