前言
Spring Security 是一個(gè)安全框架, 可以簡單地認(rèn)為 Spring Security 是放在用戶和 Spring 應(yīng)用之間的一個(gè)安全屏障, 每一個(gè) web 請求都先要經(jīng)過 Spring Security 進(jìn)行 Authenticate 和 Authoration 驗(yàn)證

核心組件
SecurityContextHolder
SecurityContextHolder它持有的是安全上下文(security context)的信息。當(dāng)前操作的用戶是誰,該用戶是否已經(jīng)被認(rèn)證,他擁有哪些角色權(quán)等等,這些都被保存在SecurityContextHolder中。SecurityContextHolder默認(rèn)使用ThreadLocal 策略來存儲(chǔ)認(rèn)證信息??吹?code>ThreadLocal 也就意味著,這是一種與線程綁定的策略。在web環(huán)境下,Spring Security在用戶登錄時(shí)自動(dòng)綁定認(rèn)證信息到當(dāng)前線程,在用戶退出時(shí),自動(dòng)清除當(dāng)前線程的認(rèn)證信息
看源碼他有靜態(tài)方法
//獲取 上下文
public static SecurityContext getContext() {
return strategy.getContext();
}
//清除上下文
public static void clearContext() {
strategy.clearContext();
}
SecurityContextHolder.getContext().getAuthentication().getPrincipal()
getAuthentication()返回了認(rèn)證信息,getPrincipal()返回了身份信息
UserDetails便是Spring對身份信息封裝的一個(gè)接口
SecurityContext
安全上下文,主要持有Authentication對象,如果用戶未鑒權(quán),那Authentication對象將會(huì)是空的。看源碼可知
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
鑒權(quán)對象,該對象主要包含了用戶的詳細(xì)信息(UserDetails)和用戶鑒權(quán)時(shí)所需要的信息,如用戶提交的用戶名密碼、Remember-me Token,或者digest hash值等,按不同鑒權(quán)方式使用不同的Authentication實(shí)現(xiàn)
看源碼可知道
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中是最高級別的身份/認(rèn)證的抽象。由這個(gè)頂級接口,我們可以得到用戶擁有的權(quán)限信息列表,密碼,用戶細(xì)節(jié)信息,用戶身份信息,認(rèn)證信息。getAuthorities(),權(quán)限信息列表,默認(rèn)是GrantedAuthority接口的一些實(shí)現(xiàn)類,通常是代表權(quán)限信息的一系列字符串。getCredentials(),密碼信息,用戶輸入的密碼字符串,在認(rèn)證過后通常會(huì)被移除,用于保障安全。getDetails(),細(xì)節(jié)信息,web應(yīng)用中的實(shí)現(xiàn)接口通常為 WebAuthenticationDetails,它記錄了訪問者的ip地址和sessionId的值。getPrincipal(),敲黑板!??!最重要的身份信息,大部分情況下返回的是UserDetails接口的實(shí)現(xiàn)類,也是框架中的常用接口之一
注意
GrantedAuthority該接口表示了當(dāng)前用戶所擁有的權(quán)限(或者角色)信息。這些信息由授權(quán)負(fù)責(zé)對象AccessDecisionManager來使用,并決定最終用戶是否可以訪問某資源(URL或方法調(diào)用或域?qū)ο螅?。鑒權(quán)時(shí)并不會(huì)使用到該對象
UserDetails
這個(gè)接口規(guī)范了用戶詳細(xì)信息所擁有的字段,譬如用戶名、密碼、賬號(hào)是否過期、是否鎖定等。在Spring Security中,獲取當(dāng)前登錄的用戶的信息,一般情況是需要在這個(gè)接口上面進(jìn)行擴(kuò)展,用來對接自己系統(tǒng)的用戶
看源碼可知
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
這個(gè)接口只提供一個(gè)接口loadUserByUsername(String username),這個(gè)接口非常重要,一般情況我們都是通過擴(kuò)展這個(gè)接口來顯示獲取我們的用戶信息,用戶登陸時(shí)傳遞的用戶名和密碼也是通過這里這查找出來的用戶名和密碼進(jìn)行校驗(yàn),但是真正的校驗(yàn)不在這里,而是由AuthenticationManager以及AuthenticationProvider負(fù)責(zé)的,需要強(qiáng)調(diào)的是,如果用戶不存在,不應(yīng)返回NULL,而要拋出異常UsernameNotFoundException
看源碼可知
package org.springframework.security.core.userdetails;
public interface UserDetailsService {
UserDetails loadUserByUsername(String var1) throws UsernameNotFoundException;
}
Spring Security安全身份認(rèn)證流程原理
用戶名和密碼被過濾器獲取到,封裝成
Authentication,通常情況下是UsernamePasswordAuthenticationToken這個(gè)實(shí)現(xiàn)類。AuthenticationManager身份管理器負(fù)責(zé)驗(yàn)證這個(gè)Authentication認(rèn)證成功后,
AuthenticationManager身份管理器返回一個(gè)被填充滿了信息的(包括上面提到的權(quán)限信息,身份信息,細(xì)節(jié)信息,但密碼通常會(huì)被移除)Authentication實(shí)例。SecurityContextHolder安全上下文容器將第3步填充了信息的Authentication,通過SecurityContextHolder.getContext().setAuthentication()方法,設(shè)置到其中。
AuthenticationManager
初次接觸Spring Security的朋友相信會(huì)被AuthenticationManager,ProviderManager ,AuthenticationProvider …這么多相似的Spring認(rèn)證類搞得暈頭轉(zhuǎn)向,但只要稍微梳理一下就可以理解清楚它們的聯(lián)系和設(shè)計(jì)者的用意。
AuthenticationManager(接口)是認(rèn)證相關(guān)的核心接口,也是發(fā)起認(rèn)證的出發(fā)點(diǎn),因?yàn)樵趯?shí)際需求中,我們可能會(huì)允許用戶使用用戶名+密碼登錄,同時(shí)允許用戶使用郵箱+密碼,手機(jī)號(hào)碼+密碼登錄,甚至,可能允許用戶使用指紋登錄(還有這樣的操作?沒想到吧),所以說AuthenticationManager一般不直接認(rèn)證,
AuthenticationManager接口的常用實(shí)現(xiàn)類ProviderManager 內(nèi)部會(huì)維護(hù)一個(gè)List<AuthenticationProvider>列表,存放多種認(rèn)證方式,實(shí)際上這是委托者模式的應(yīng)用(Delegate)。
也就是說,核心的認(rèn)證入口始終只有一個(gè):AuthenticationManager,不同的認(rèn)證方式:用戶名+密碼(UsernamePasswordAuthenticationToken),郵箱+密碼,手機(jī)號(hào)碼+密碼登錄則對應(yīng)了三個(gè)AuthenticationProvider。這樣一來就好理解多了
UserDetails和UserDetailsService
UserDetails
上面不斷提到了UserDetails這個(gè)接口,它代表了最詳細(xì)的用戶信息,這個(gè)接口涵蓋了一些必要的用戶信息字段,我們一般都需要對它進(jìn)行必要的擴(kuò)展。
它和Authentication接口很類似,比如它們都擁有username,authorities,區(qū)分他們也是本文的重點(diǎn)內(nèi)容之一。
Authentication的getCredentials()與UserDetails中的getPassword()需要被區(qū)分對待,前者是用戶提交的密碼憑證,后者是用戶正確的密碼,認(rèn)證器其實(shí)就是對這兩者的比對。Authentication中的getAuthorities()實(shí)際是由UserDetails的getAuthorities()傳遞而形成的。還記得Authentication接口中的getUserDetails()方法嗎?其中的UserDetails用戶詳細(xì)信息便是經(jīng)過了AuthenticationProvider之后被填充的。
UserDetailsService
UserDetailsService和AuthenticationProvider兩者的職責(zé)常常被人們搞混,UserDetailsService只負(fù)責(zé)從特定的地方加載用戶信息,可以是數(shù)據(jù)庫、redis緩存、接口等

全局獲取用戶信息方式
- 通過注入 Principal 接口獲取用戶信息
在運(yùn)行過程中,Spring 會(huì)將 Username、Password、Authentication、Token 注入到 Principal 接口中,我們可以直接在controller獲取使用
@GetMapping("/home")
@ApiOperation("用戶中心")
public Result getUserHome(Principal principal) {
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken=(UsernamePasswordAuthenticationToken)principal;
return ResultResponse.success(usernamePasswordAuthenticationToken.getPrincipal());
}
- 使用 @AuthenticationPrincipal 注解參數(shù)的方式
@GetMapping("/home")
@ApiOperation("用戶中心")
public Result getUserHome(@AuthenticationPrincipal cn.soboys.kmall.security.entity.User user ) {
return ResultResponse.success(user);
}
- 全局上下文獲取
由于獲取當(dāng)前用戶的用戶名是一種比較常見的需求,其實(shí) Spring Security 在 Authentication 中的實(shí)現(xiàn)類中已經(jīng)為我們做了相關(guān)實(shí)現(xiàn),所以獲取當(dāng)前用戶的用戶名有如下更簡單的方式
@RestController
public class HelloController {
@GetMapping("/hello")
public String hello() {
return "當(dāng)前登錄用戶:" + SecurityContextHolder.getContext().getAuthentication().getName();
}
}
- 獲取當(dāng)前登錄用戶的 UserDetails 實(shí)例,然后再轉(zhuǎn)換成自定義的用戶實(shí)體類 User,這樣便能獲取用戶的 ID 等信息
@RestController
public class HelloController {
@GetMapping("/hello")
public String hello() {
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
User user = (User)principal;
return "當(dāng)前登錄用戶信息:" + user.toString();
}
}
- 異步方法中獲取用戶信息
Spring Security在默認(rèn)情況下無法在使用@Async注解的方法中獲取當(dāng)前登錄用戶的。若想在@Async方法中獲取當(dāng)前登錄用戶,則需要調(diào)用SecurityContextHolder.setStrategyName方法并設(shè)置相關(guān)的策略
參考