天天看點

SpringBoot Spring Security 核心元件 認證流程 使用者權限資訊擷取超級細解

原文

前言 Spring Security 是一個安全架構, 可以簡單地認為 Spring Security 是放在使用者和 Spring 應用之間的一個安全屏障, 每一個 web 請求都先要經過 Spring Security 進行 Authenticate 和 Authoration 驗證

SpringBoot Spring Security 核心元件 認證流程 使用者權限資訊擷取超級細解

核心元件

SecurityContextHolder

SecurityContextHolder它持有的是安全上下文

(security context)的資訊。目前操作的使用者是誰,該使用者是否已經被認證,他擁有哪些角色權等等,這些都被儲存在SecurityContextHolder中。SecurityContextHolder預設使用ThreadLocal 政策來存儲認證資訊。看到

ThreadLocal

也就意味着,這是一種與線程綁定的政策。在web環境下,Spring Security在使用者登入時自動綁定認證資訊到目前線程,在使用者退出時,自動清除目前線程的認證資訊

看源碼他有靜态方法

//擷取 上下文
  public static SecurityContext getContext() {
        return strategy.getContext();
    }
  //清除上下文  
  public static void clearContext() {
        strategy.clearContext();
    }   
           

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;
}
           
  1. Authentication

    是spring security包中的接口,直接繼承自Principal類,而Principal是位于java.security包中的。可以見得,Authentication在spring security中是最進階别的身份/認證的抽象。由這個頂級接口,我們可以得到使用者擁有的權限資訊清單,密碼,使用者細節資訊,使用者身份資訊,認證資訊。
  2. getAuthorities()

    ,權限資訊清單,預設是GrantedAuthority接口的一些實作類,通常是代表權限資訊的一系列字元串。
  3. getCredentials()

    ,密碼資訊,使用者輸入的密碼字元串,在認證過後通常會被移除,用于保障安全。
  4. getDetails()

    ,細節資訊,web應用中的實作接口通常為 WebAuthenticationDetails,它記錄了通路者的ip位址和sessionId的值。
  5. 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安全身份認證流程原理

  1. 使用者名和密碼被過濾器擷取到,封裝成

    Authentication

    ,通常情況下是

    UsernamePasswordAuthenticationToken

    這個實作類。
  2. AuthenticationManager

    身份管理器負責驗證這個

    Authentication

  3. 認證成功後,

    AuthenticationManager

    身份管理器傳回一個被填充滿了資訊的(包括上面提到的權限資訊,身份資訊,細節資訊,但密碼通常會被移除)

    Authentication

    執行個體。
  4. 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緩存、接口等

SpringBoot Spring Security 核心元件 認證流程 使用者權限資訊擷取超級細解

全局擷取使用者資訊方式

  1. 通過注入 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());
    }
           

2.使用 @AuthenticationPrincipal 注解參數的方式

@GetMapping("/home")
    @ApiOperation("使用者中心")
    public Result getUserHome(@AuthenticationPrincipal cn.soboys.kmall.security.entity.User user ) {
        return ResultResponse.success(user);
    }
           

3.全局上下文擷取

由于擷取目前使用者的使用者名是一種比較常見的需求,其實 Spring Security 在 Authentication 中的實作類中已經為我們做了相關實作,是以擷取目前使用者的使用者名有如下更簡單的方式

@RestController
public class HelloController {
 
    @GetMapping("/hello")
    public String hello() {
        return "目前登入使用者:" + SecurityContextHolder.getContext().getAuthentication().getName();
    }
}
           

4.擷取目前登入使用者的 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();
    }
}
           

5.異步方法中擷取使用者資訊點選獲得更多實戰案例及大廠面試題

Spring Security在預設情況下無法在使用@Async注解的方法中擷取目前登入使用者的。若想在@Async方法中擷取目前登入使用者,則需要調用

SecurityContextHolder.setStrategyName

方法并設定相關的政策

繼續閱讀