SpringBoot Spring Security 核心組件 認(rèn)證流程 用戶權(quán)限信息獲取詳細(xì)講解

前言

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

image

核心組件

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;
}
  1. Authentication是spring security包中的接口,直接繼承自Principal類,而Principal是位于java.security包中的??梢砸姷?,Authentication在spring security中是最高級別的身份/認(rèn)證的抽象。由這個(gè)頂級接口,我們可以得到用戶擁有的權(quán)限信息列表,密碼,用戶細(xì)節(jié)信息,用戶身份信息,認(rèn)證信息。

  2. getAuthorities(),權(quán)限信息列表,默認(rèn)是GrantedAuthority接口的一些實(shí)現(xiàn)類,通常是代表權(quán)限信息的一系列字符串。

  3. getCredentials(),密碼信息,用戶輸入的密碼字符串,在認(rèn)證過后通常會(huì)被移除,用于保障安全。

  4. getDetails(),細(xì)節(jié)信息,web應(yīng)用中的實(shí)現(xiàn)接口通常為 WebAuthenticationDetails,它記錄了訪問者的ip地址和sessionId的值。

  5. 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)證流程原理

  1. 用戶名和密碼被過濾器獲取到,封裝成Authentication,通常情況下是UsernamePasswordAuthenticationToken這個(gè)實(shí)現(xiàn)類。

  2. AuthenticationManager 身份管理器負(fù)責(zé)驗(yàn)證這個(gè)Authentication

  3. 認(rèn)證成功后,AuthenticationManager身份管理器返回一個(gè)被填充滿了信息的(包括上面提到的權(quán)限信息,身份信息,細(xì)節(jié)信息,但密碼通常會(huì)被移除)Authentication實(shí)例。

  4. 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緩存、接口等

image

全局獲取用戶信息方式

  1. 通過注入 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());
    }
  1. 使用 @AuthenticationPrincipal 注解參數(shù)的方式
   @GetMapping("/home")
    @ApiOperation("用戶中心")
    public Result getUserHome(@AuthenticationPrincipal cn.soboys.kmall.security.entity.User user ) {
        return ResultResponse.success(user);
    }
  1. 全局上下文獲取

由于獲取當(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();
    }
}
  1. 獲取當(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();
    }
}
  1. 異步方法中獲取用戶信息

Spring Security在默認(rèn)情況下無法在使用@Async注解的方法中獲取當(dāng)前登錄用戶的。若想在@Async方法中獲取當(dāng)前登錄用戶,則需要調(diào)用SecurityContextHolder.setStrategyName方法并設(shè)置相關(guān)的策略

參考

  1. Spring Security在@Async異步方法中獲取登錄用戶
  2. Spring Security
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容