1. 程式人生 > >【Spring】SpringSecurity無法獲取當前登入使用者問題

【Spring】SpringSecurity無法獲取當前登入使用者問題

問題描述:專案的許可權是用SpringSecurity實現的,專案中有個需求,實現需求的邏輯如下:

啟動SpringBoot後啟動一個執行緒去監聽redis訊息佇列,對資料進行加工並儲存到庫裡,需要儲存建立人(createBy),而建立人正是當前登入使用者,但一直獲取不到當前登入使用者。

解釋如下:

我們將當前應用security上下文的所有資料儲存在SecurityContextHolder裡面,這些資料包括應用系統中使用的principle資料。SecurityContextHolder預設使用ThreadLocal區域性變數儲存這些資料,這意味著security上下文對於相同的正在執行的執行緒的方法是可用的,即使security上下文沒有顯式地將引數傳遞給這些方法,如果希望在當前principal請求處理完畢後要清理這些執行緒,這樣使用ThreadLocal區域性變數會是非常安全的。這樣就保證了本執行緒內所有的方法都可以獲得SecurityContext物件,

當然,Spring Security會自動的處理這些問題,所以你不需要有任何的擔心。

SecurityContextHolder還有其他兩種模式,分別為SecurityContextHolder.MODE_GLOBAL和SecurityContextHolder.MODE_INHERITABLETHREADLOCAL,前者表示SecurityContextHolder物件的全域性的,應用中所有執行緒都可以訪問;後者用於執行緒有父子關係的情境中,執行緒希望自己的子執行緒和自己有相同的安全性。

大部分情況下我們不需要修改預設的配置,ThreadLocal是最常用也是最合適大部分應用的。

獲得認證主體資訊

我們可以用下面的程式碼段獲得認證的主體資訊

Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
 
if (principal instanceof UserDetails) {
  String username = ((UserDetails)principal).getUsername();
} else {
  String username = principal.toString();
}

第一行程式碼返回的是一個UserDetails型別的例項,其中包含了username,password和許可權等資訊,當然,我們也可以通過實現這個介面自定義我們自己的UserDetails例項,給我們自己的應用使用,以符合需要的業務邏輯。

UserDetailsService

上面說到可以自定義UserDetails例項,我們怎麼獲得這個例項呢,就需要通過UserDetailsService來實現,這個介面只有一個方法

UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;

這個方法接受一個使用者名稱引數,返回UserDetails例項。

認證成功之後,UserDetails物件用來構建Authentication物件並存放在SecurityContextHolder中,所以,我們需要的使用者資訊都可以通過SecurityContextHolder拿到。

GrantedAuthority

Authentication物件還提供了getAuthorities方法,獲取使用者被賦予的許可權,許可權通常對應著角色,什麼角色對應著什麼樣的訪問許可權,例如ADMIN_ROLE可以訪問/admin下的內容,其他角色則無權訪問。

GrantedAuthority物件也通常由UserDetailsService例項獲取。

總結

我們上面提到了如下幾個物件:

SecurityContextHolder:提供對SecurityContext的訪問
SecurityContext,:持有Authentication物件和其他可能需要的資訊
Authentication:Spring Security方式的認證主體
GrantedAuthority:對認證主題的應用層面的授權
UserDetails:構建Authentication物件必須的資訊,可以自定義,可能需要訪問DB得到
UserDetailsService:通過username構建UserDetails物件