匿名認證

概述

普遍認為,採用“預設拒絕”姿態是良好的安全實踐,即明確指定允許的內容,並拒絕其他所有內容。特別是對於 Web 應用程式,定義對未認證使用者可訪問的內容與此類似。許多站點要求除了少數 URL(例如首頁和登入頁面)之外,使用者必須經過認證才能訪問任何內容。在這種情況下,最簡單的做法是為這些特定的 URL 定義訪問配置屬性,而不是為每個受保護的資源定義。換句話說,有時最好預設要求 ROLE_SOMETHING,只允許某些例外規則,例如應用程式的登入、登出和首頁。您也可以將這些頁面完全排除在過濾器鏈之外,從而繞過訪問控制檢查,但這可能因其他原因而不可取,特別是如果這些頁面對於已認證使用者表現不同的話。

這就是我們所說的匿名認證。請注意,“匿名認證”的使用者與未認證使用者之間沒有真正的概念差異。Spring Security 的匿名認證只是為您配置訪問控制屬性提供了一種更便捷的方式。即使 SecurityContextHolder 中實際存在匿名認證物件,對 servlet API 呼叫(例如 getCallerPrincipal)的呼叫仍然返回 null。

在其他情況下,匿名認證也很有用,例如當審計攔截器查詢 SecurityContextHolder 以識別哪個主體負責給定操作時。如果類知道 SecurityContextHolder 總是包含一個 Authentication 物件而永遠不包含 null,那麼它們的編寫會更健壯。

配置

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

三個類協同工作以提供匿名認證功能。AnonymousAuthenticationTokenAuthentication 的實現,儲存應用於匿名主體的 GrantedAuthority 例項。有一個相應的 AnonymousAuthenticationProvider,它被連結到 ProviderManager 中,以便接受 AnonymousAuthenticationToken 例項。最後,AnonymousAuthenticationFilter 被連結在正常的認證機制之後,如果在 SecurityContextHolder 中沒有現有 Authentication,則自動新增一個 AnonymousAuthenticationToken。過濾器和認證提供者定義如下

<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>

過濾器和認證提供者之間共享 key,這樣前者建立的令牌會被後者接受

key 屬性的使用不應被視為在此提供了任何真正的安全性。它僅僅是記錄管理操作。如果您在認證客戶端可以構造 Authentication 物件的情況下(例如使用 RMI 呼叫)共享包含 AnonymousAuthenticationProviderProviderManager,那麼惡意客戶端可以提交自己建立的 AnonymousAuthenticationToken(帶有選定的使用者名稱和許可權列表)。如果 key 是可猜測的或可以被發現,該令牌將被匿名提供者接受。在正常使用中這不是問題。但是,如果您使用 RMI,則應使用自定義的 ProviderManager,該管理器省略匿名提供者,而不是共享用於 HTTP 認證機制的管理器。

userAttribute 的形式為 usernameInTheAuthenticationToken,grantedAuthority[,grantedAuthority]。與 InMemoryDaoImpluserMap 屬性的等號後面的語法相同。

如前所述,匿名認證的好處是所有 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 方法更強大,因為它允許您區分匿名、記住我(remember-me)和完全認證使用者。但是,如果您不需要此功能,則可以繼續使用由 Spring Security 的標準 RoleVoter 處理的 ROLE_ANONYMOUS

在 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 解析引數,當請求是匿名時,它返回 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