SpringBoot Spring Security 核心組件 認證流程 用戶許可權資訊獲取詳細講解
- 2021 年 8 月 24 日
- 筆記
- springboot
前言
Spring Security 是一個安全框架, 可以簡單地認為 Spring Security 是放在用戶和 Spring 應用之間的一個安全螢幕障, 每一個 web 請求都先要經過 Spring Security 進行 Authenticate 和 Authoration 驗證
核心組件
SecurityContextHolder
SecurityContextHolder它持有的是安全上下文
(security context)的資訊。當前操作的用戶是誰,該用戶是否已經被認證,他擁有哪些角色權等等,這些都被保存在SecurityContextHolder中。SecurityContextHolder默認使用ThreadLocal 策略來存儲認證資訊。看到ThreadLocal
也就意味著,這是一種與執行緒綁定的策略。在web環境下,Spring Security在用戶登錄時自動綁定認證資訊到當前執行緒,在用戶退出時,自動清除當前執行緒的認證資訊
看源碼他有靜態方法
//獲取 上下文
public static SecurityContext getContext() {
return strategy.getContext();
}
//清除上下文
public static void clearContext() {
strategy.clearContext();
}
SecurityContextHolder.getContext().getAuthentication().getPrincipal()
getAuthentication()
返回了認證資訊,getPrincipal()
返回了身份資訊
UserDetails
便是Spring對身份資訊封裝的一個介面
SecurityContext
安全上下文,主要持有Authentication
對象,如果用戶未鑒權,那Authentication對象將會是空的。看源碼可知
package org.springframework.security.core.context;
import java.io.Serializable;
import org.springframework.security.core.Authentication;
public interface SecurityContext extends Serializable {
Authentication getAuthentication();
void setAuthentication(Authentication var1);
}
Authentication
鑒權對象,該對象主要包含了用戶的詳細資訊(UserDetails)
和用戶鑒權時所需要的資訊,如用戶提交的用戶名密碼、Remember-me Token,或者digest hash值等,按不同鑒權方式使用不同的Authentication
實現
看源碼可知道
package org.springframework.security.core;
import java.io.Serializable;
import java.security.Principal;
import java.util.Collection;
public interface Authentication extends Principal, Serializable {
Collection<? extends GrantedAuthority> getAuthorities();
Object getCredentials();
Object getDetails();
Object getPrincipal();
boolean isAuthenticated();
void setAuthenticated(boolean var1) throws IllegalArgumentException;
}
-
Authentication
是spring security包中的介面,直接繼承自Principal類,而Principal是位於java.security包中的。可以見得,Authentication在spring security中是最高級別的身份/認證的抽象。由這個頂級介面,我們可以得到用戶擁有的許可權資訊列表,密碼,用戶細節資訊,用戶身份資訊,認證資訊。 -
getAuthorities()
,許可權資訊列表,默認是GrantedAuthority介面的一些實現類,通常是代表許可權資訊的一系列字元串。 -
getCredentials()
,密碼資訊,用戶輸入的密碼字元串,在認證過後通常會被移除,用於保障安全。 -
getDetails()
,細節資訊,web應用中的實現介面通常為 WebAuthenticationDetails,它記錄了訪問者的ip地址和sessionId的值。 -
getPrincipal()
,敲黑板!!!最重要的身份資訊,大部分情況下返回的是UserDetails介面的實現類,也是框架中的常用介面之一
注意
GrantedAuthority
該介面表示了當前用戶所擁有的許可權(或者角色)資訊。這些資訊由授權負責對象AccessDecisionManager
來使用,並決定最終用戶是否可以訪問某資源
(URL或方法調用或域對象)。鑒權時並不會使用到該對象
UserDetails
這個介面規範了用戶詳細資訊所擁有的欄位,譬如用戶名、密碼、帳號是否過期、是否鎖定等。在Spring Security中,獲取當前登錄的用戶的資訊,一般情況是需要在這個介面上面進行擴展
,用來對接自己系統的用戶
看源碼可知
package org.springframework.security.core.userdetails;
import java.io.Serializable;
import java.util.Collection;
import org.springframework.security.core.GrantedAuthority;
public interface UserDetails extends Serializable {
Collection<? extends GrantedAuthority> getAuthorities();
String getPassword();
String getUsername();
boolean isAccountNonExpired();
boolean isAccountNonLocked();
boolean isCredentialsNonExpired();
boolean isEnabled();
}
UserDetailsService
這個介面只提供一個介面loadUserByUsername(String username)
,這個介面非常重要,
一般情況我們都是通過擴展
這個介面來顯示獲取我們的用戶資訊,用戶登陸時傳遞的用戶名和密碼也是通過這裡這查找出來的用戶名和密碼進行校驗,但是真正的校驗不在這裡,而是由AuthenticationManager
以及AuthenticationProvider
負責的,需要強調的是,如果用戶不存在,不應返回NULL
,而要拋出異常UsernameNotFoundException
看源碼可知
package org.springframework.security.core.userdetails;
public interface UserDetailsService {
UserDetails loadUserByUsername(String var1) throws UsernameNotFoundException;
}
Spring Security安全身份認證流程原理
-
用戶名和密碼被過濾器獲取到,封裝成
Authentication
,通常情況下是UsernamePasswordAuthenticationToken
這個實現類。 -
AuthenticationManager
身份管理器負責驗證這個Authentication
-
認證成功後,
AuthenticationManager
身份管理器返回一個被填充滿了資訊的(包括上面提到的許可權資訊,身份資訊,細節資訊,但密碼通常會被移除)Authentication
實例。 -
SecurityContextHolder
安全上下文容器將第3步填充了資訊的Authentication
,通過SecurityContextHolder.getContext().setAuthentication()
方法,設置到其中。
AuthenticationManager
初次接觸Spring Security的朋友相信會被AuthenticationManager,ProviderManager ,AuthenticationProvider …這麼多相似的Spring認證類搞得暈頭轉向,但只要稍微梳理一下就可以理解清楚它們的聯繫和設計者的用意。
AuthenticationManager
(介面)是認證相關的核心介面
,也是發起認證的出發點,因為在實際需求中,我們可能會允許用戶使用用戶名+密碼登錄,同時允許用戶使用郵箱+密碼,手機號碼+密碼登錄,甚至,可能允許用戶使用指紋登錄(還有這樣的操作?沒想到吧),所以說AuthenticationManager
一般不直接認證,
AuthenticationManager
介面的常用實現類ProviderManager
內部會維護一個List<AuthenticationProvider>
列表,存放多種認證方式,實際上這是委託者模式的應用(Delegate)。
也就是說,核心的認證入口始終只有一個:AuthenticationManager
,不同的認證方式:用戶名+密碼(UsernamePasswordAuthenticationToken
),郵箱+密碼,手機號碼+密碼登錄則對應了三個AuthenticationProvider
。這樣一來就好理解多了
UserDetails和UserDetailsService
UserDetails
上面不斷提到了UserDetails
這個介面,它代表了最詳細的用戶資訊,這個介面涵蓋了一些必要的用戶資訊欄位,我們一般都需要對它進行必要的擴展。
它和Authentication
介面很類似,比如它們都擁有username,authorities,區分他們也是本文的重點內容之一。
Authentication的getCredentials()與UserDetails中的getPassword()
需要被區分對待,前者是用戶提交的密碼憑證,後者是用戶正確的密碼,認證器其實就是對這兩者的比對。Authentication中的getAuthorities()實際是由UserDetails的getAuthorities()傳遞而形成的。還記得Authentication介面中的getUserDetails()方法嗎?其中的UserDetails用戶詳細資訊便是經過了AuthenticationProvider之後被填充的。
UserDetailsService
UserDetailsService和AuthenticationProvider兩者的職責常常被人們搞混,UserDetailsService只負責從特定的地方載入用戶資訊,可以是資料庫、redis快取、介面等
全局獲取用戶資訊方式
- 通過注入 Principal 介面獲取用戶資訊
在運行過程中,Spring 會將 Username、Password、Authentication、Token 注入到 Principal 介面中,我們可以直接在controller獲取使用
@GetMapping("/home")
@ApiOperation("用戶中心")
public Result getUserHome(Principal principal) {
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken=(UsernamePasswordAuthenticationToken)principal;
return ResultResponse.success(usernamePasswordAuthenticationToken.getPrincipal());
}
- 使用 @AuthenticationPrincipal 註解參數的方式
@GetMapping("/home")
@ApiOperation("用戶中心")
public Result getUserHome(@AuthenticationPrincipal cn.soboys.kmall.security.entity.User user ) {
return ResultResponse.success(user);
}
- 全局上下文獲取
由於獲取當前用戶的用戶名是一種比較常見的需求,其實 Spring Security 在 Authentication 中的實現類中已經為我們做了相關實現,所以獲取當前用戶的用戶名有如下更簡單的方式
@RestController
public class HelloController {
@GetMapping("/hello")
public String hello() {
return "當前登錄用戶:" + SecurityContextHolder.getContext().getAuthentication().getName();
}
}
- 獲取當前登錄用戶的 UserDetails 實例,然後再轉換成自定義的用戶實體類 User,這樣便能獲取用戶的 ID 等資訊
@RestController
public class HelloController {
@GetMapping("/hello")
public String hello() {
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
User user = (User)principal;
return "當前登錄用戶資訊:" + user.toString();
}
}
- 非同步方法中獲取用戶資訊
Spring Security在默認情況下無法在使用@Async註解的方法中獲取當前登錄用戶的。若想在@Async方法中獲取當前登錄用戶,則需要調用SecurityContextHolder.setStrategyName
方法並設置相關的策略
參考