匿名認證
概述
通常認為,採用“預設拒絕”立場是一種良好的安全實踐,即明確指定允許的內容並拒絕所有其他內容。定義未認證使用者可訪問的內容也是類似的情況,特別是對於 Web 應用程式。許多網站要求使用者必須經過認證才能訪問除少數 URL(例如主頁和登入頁)之外的任何內容。在這種情況下,為這些特定的 URL 定義訪問配置屬性比為每個受保護的資源定義訪問配置屬性更容易。換句話說,有時最好預設要求 ROLE_SOMETHING,並只允許此規則的某些例外,例如應用程式的登入、登出和主頁。您也可以完全從過濾器鏈中省略這些頁面,從而繞過訪問控制檢查,但這可能出於其他原因而不可取,特別是如果頁面對經過認證的使用者有不同的行為。
這就是我們所說的匿名認證。請注意,“匿名認證”的使用者與未認證的使用者之間沒有真正的概念區別。Spring Security 的匿名認證只是提供了一種更方便的方式來配置訪問控制屬性。對 servlet API 呼叫的呼叫(例如 getCallerPrincipal)仍然返回 null,即使 SecurityContextHolder 中實際上存在匿名認證物件。
在其他情況下,匿名認證也很有用,例如當審計攔截器查詢 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>
key 在過濾器和認證提供者之間共享,因此前者建立的令牌會被後者接受。
|
不應將 |
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"
}
}
將始終返回“非匿名”,即使對於匿名請求也是如此。原因是 Spring MVC 使用 HttpServletRequest#getPrincipal 解析引數,當請求是匿名時,該引數為 null。
如果您希望在匿名請求中獲取 Authentication,請改用 @CurrentSecurityContext
-
Java
-
Kotlin
@GetMapping("/")
public String method(@CurrentSecurityContext SecurityContext context) {
if (context.getAuthentication() instanceOf AnonymousAuthenticationToken) {
return "anonymous"
} else {
return "not anonymous"
}
}
@GetMapping("/")
fun method(@CurrentSecurityContext context : SecurityContext) : String {
return if (context!!.authentication is AnonymousAuthenticationToken) {
"anonymous"
} else {
"not anonymous"
}
}