【Spring Boot】SecurityContextHolder.getContext().getAuthentication()爲null的情況

SecurityContextHolder.getContext().getAuthentication()爲null的情況

問題描述

在登錄的時候,用如下方法獲取輸入的用戶名:

/**
 * 獲得當前用戶名稱
 */
private String getUsername() {
    String username = SecurityContextHolder.getContext().getAuthentication().getName();
    System.out.println("username= " + username);
    return username;
}

但是實際卻不能取到用戶名,錯誤日誌如下:
錯誤異常情況

Spring Security 的基本組件 SecurityContextHolder

Spring Security 中最基本的組件應該是SecurityContextHolder了。這是一個工具類,只提供一些靜態方法。這個工具類的目的是用來保存應用程序中當前使用人的安全上下文。

SecurityContextHolder的工作原理

缺省工作模式 MODE_THREADLOCAL

我們知道,一個應用同時可能有多個使用者,每個使用者對應不同的安全上下文,那麼SecurityContextHolder是怎麼保存這些安全上下文的呢 ?缺省情況下,SecurityContextHolder使用了ThreadLocal機制來保存每個使用者的安全上下文。這意味着,只要針對某個使用者的邏輯執行都是在同一個線程中進行即使不在各個方法之間以參數的形式傳遞其安全上下文,各個方法也能通過SecurityContextHolder工具獲取到該安全上下文。只要在處理完當前使用者的請求之後注意清除ThreadLocal中的安全上下文,這種使用ThreadLocal的方式是很安全的。當然在Spring Security中,這些工作已經被Spring Security自動處理,開發人員不用擔心這一點。
這裏提到的SecurityContextHolder基於ThreadLocal的工作方式天然很適合Servlet Web應用,因爲缺省情況下根據Servlet規範,一個Servlet request的處理不管經歷了多少個Filter,自始至終都由同一個線程來完成。

注意: 這裏講的是一個Servlet request的處理不管經歷了多少個Filter,自始至終都由同一個線程來完成;而對於同一個使用者的不同Servlet request,它們在服務端被處理時,使用的可不一定是同一個線程(存在由同一個線程處理的可能性但不確保)。

其他工作模式

有一些應用並不適合使用ThreadLocal模式,那麼還能不能使用SecurityContextHolder了呢?答案是可以的。SecurityContextHolder還提供了其他工作模式。
比如有些應用,像Java Swing客戶端應用,它就可能希望JVM中所有的線程使用同一個安全上下文。此時我們可以在啓動階段將SecurityContextHolder配置成全局策略MODE_GLOBAL。
還有其他的一些應用會有自己的線程創建,並且希望這些新建線程也能使用創建者的安全上下文。這種效果,可以通過將SecurityContextHolder配置成MODE_INHERITABLETHREADLOCAL策略達到。

使用SecurityContextHolder

獲取當前用戶信息

在SecurityContextHolder中保存的是當前訪問者的信息。Spring Security使用一個Authentication對象來表示這個信息。一般情況下,我們都不需要創建這個對象,在登錄過程中,Spring Security已經創建了該對象並幫我們放到了SecurityContextHolder中。從SecurityContextHolder中獲取這個對象也是很簡單的。比如,獲取當前登錄用戶的用戶名,可以這樣:

String username = "";
// 獲取安全上下文對象,就是那個保存在 ThreadLocal 裏面的安全上下文對象
// 總是不爲null(如果不存在,則創建一個authentication屬性爲null的empty安全上下文對象)
SecurityContext securityContext = SecurityContextHolder.getContext();
// 獲取當前認證了的 principal(當事人),或者 request token (令牌)
// 如果沒有認證,會是 null,該例子是認證之後的情況
Authentication authentication = securityContext.getAuthentication();
// 獲取當事人信息對象,返回結果是Object類型,但實際上可以是應用程序自定義的帶有更多應用相關信息的某個類型。
// 很多情況下,該對象是Spring Security核心接口UserDetails的一個實現類,
// 可以把UserDetails想像成數據庫中保存的一個用戶信息到SecurityContextHolder中Spring Security需要的用戶信息格式的一個適配器。
Object principal = authentication.getPrincipal();
if (principal instanceof UserDetails) {
    username = ((UserDetails) principal).getUsername();
} else {
    username = principal.toString();
}

修改SecurityContextHolder的工作模式

綜上所述,SecurityContextHolder可以工作在以下三種模式之一:

  • MODE_THREADLOCAL (缺省工作模式)
  • MODE_GLOBAL
  • MODE_INHERITABLETHREADLOCAL
    修改SecurityContextHolder的工作模式有兩種方法 :
    • 設置一個系統屬性(system.properties) : spring.security.strategy; SecurityContextHolder會自動從該系統屬性中嘗試獲取被設定的工作模式
    • 調用SecurityContextHolder靜態方法setStrategyName(): 程序化方式主動設置工作模式的方法

解決方案

暫無

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