天天看點

淺析基于Spring Security 的身份認證流程

作者:馬士兵教育CTO
淺析基于Spring Security 的身份認證流程

引言

在企業級項目中,目前較為流行的的認證和授權架構主要是 Spring Security 和 Shiro。Spring Security 相對于 Shiro 具有更加豐富的功能和社群資源,但相對而言 Spring Security 的上手難度也要大于 Shiro。是以,一般中大型項目會更傾向于使用 Spring Security 架構,而小型項目多采用 Shiro 架構。

1 Spring Security 是什麼?官方是這樣介紹的。

“Spring Security is a framework that focuses on providing both authentication and authorization to Java applications. Like all Spring projects, the real power of Spring Security is found in how easily it can be extended to meet custom requirements.”

Spring Security 是一個專注于為 Java 應用程式提供身份認證和授權的架構。這句話簡明扼要地說出了 Spring Security 的兩個核心功能:

  • 身份認證(authentication),即驗證使用者身份的合法性,以判斷使用者能否登入。
  • 授權(authentication),即驗證使用者是否有權限通路某些資源或者執行某些操作。

本文将結合源碼對身份認證(authentication)的流程和核心元件進行詳細講解。

Spring Security 進行身份認證這一功能主要是通過一系列的過濾器鍊配合來實作的。在我們啟動 Spring Security 項目時可以在控制台看到 DefaultSecurityFilterChain 列印出的預設過濾器鍊。

public DefaultSecurityFilterChain(RequestMatcher requestMatcher, List<Filter> filters) { 
    logger.info("Creating filter chain: " + requestMatcher + ", " + filters); 
    this.requestMatcher = requestMatcher; this.filters = new ArrayList(filters); 
}           

1.1 Spring Security 核心過濾器

先簡要說一下過濾器(Filter),Filter 可以在伺服器作出響應前攔截使用者請求,并在攔截後修改 request 和 response,可實作一次編碼、多處應用。Filter 主要有以下兩點作用:

  1. 攔截請求:在 HttpServletRequest 到達 Servlet 之前進行攔截,檢視和修改 HttpServletRequest 的 Header 和資料。
  2. 攔截響應:在 HttpServletResponse 到達用戶端之前完成攔截,檢視和修改 HttpServletResponse 的 Header 和資料。

過濾器鍊作為 Spring Security 的核心,下圖呈現 Spring Security 過濾器鍊的一個簡要執行流程。本文将以該流程為基礎對認證流程進行剖析:

淺析基于Spring Security 的身份認證流程

Spring Security 過濾器鍊執行流程圖

  • SecurityContextPersistenceFilter:整個 Spring Security 過濾器鍊的開端。主要有兩點作用:(1)當請求到來時,檢查 Session中是否存在 SecurityContext,若不存在,則建立一個新的 SecurityContext;(2)在請求結束時,将 SecurityContext 放入 Session 中,并清空 SecurityContextHolder。
  • UsernamePasswordAuthenticationFilter:繼承自抽象類 AbstractAuthenticationProcessingFilter。當進行表單登入時,該 Filter 将使用者名和密碼封裝成 UsernamePasswordAuthenticationToken 進行驗證。
  • AnonymousAuthenticationFilter:匿名身份過濾器,一般用于匿名登入。目前面的 Filter 認證後依然沒有使用者資訊時,該Filter會生成一個匿名身份 AnonymousAuthenticationToken。
  • ExceptionTranslationFilter:異常轉換過濾器,用于處理 FilterSecurityInterceptor 抛出的異常。但是隻會處理兩類異常:AuthenticationException 和 AccessDeniedException,其它的異常它會繼續抛出。

1.2 Spring Security 核心元件

  1. SecurityContextHolder:用于擷取 SecurityContext 的靜态工具類,是Spring Security 存儲身份驗證者詳細資訊的位置。
  2. SecurityContext: 上下文對象,Authentication 對象會放在裡面。
  3. Authentication: 認證接口,定義了認證對象的資料形式。
  4. AuthenticationManager: 用于校驗 Authentication,傳回一個認證完成後的 Authentication 對象。

1、SecurityContextHolder

public class SecurityContextHolder {

    public static void clearContext() {
        strategy.clearContext();
    }
    public static SecurityContext getContext() {
        return strategy.getContext();
    }
    public static void setContext(SecurityContext context) {
        strategy.setContext(context);
    }
}           

SecurityContextHolder 用于存儲安全上下文(SecurityContext)的資訊。而如何保證使用者資訊的安全,Spring Security 采用“使用者資訊和線程綁定”的政策,SecurityContextHolder 預設采用 ThreadLocal 機制儲存使用者的 SecurityContext,在使用中可以通過 SecurityContextHolder 工具輕松擷取使用者安全上下文。這意味着,隻要是針對某個使用者的邏輯執行都是在同一個線程中進行,Spring Security 會在使用者登入時自動綁定認證資訊到目前線程,在使用者退出時也會自動清除目前線程的認證資訊。

淺析基于Spring Security 的身份認證流程

SecurityContextHolder、SecurityContext 和 Authentication 的關系如圖中所示。

Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); 
SecurityUserDetails userDetails = (SecurityUserDetails) authentication.getPrincipal();           

其中,getAuthentication() 傳回認證資訊,getPrincipal() 傳回身份資訊。 SecurityContext 是從 SecurityContextHolder 獲得的。SecurityContext 包含一個 Authentication對象。

當然,有些應用程式并不完全适合使用 ThreadLocal 模式。例如,有些應用希望能在子線程中擷取登入使用者的資訊,亦或者希望所有線程使用相同的安全上下文。 針對這些情況,SecurityContextHolder 可以在啟動時配置政策以指定您希望如何存儲上下文。

  • MODE_THREADLOCAL:SecurityContext 存儲線上程中。
  • MODE_INHERITABLETHREADLOCAL:SecurityContext 存儲線上程中,但子線程可以擷取到父線程中的 SecurityContext。
  • MODE_GLOBAL:SecurityContext 在所有線程中都相同。

2、SecurityContext

public interface SecurityContext extends Serializable { 
    Authentication getAuthentication(); 
    
    void setAuthentication(Authentication var1); 
}           

SecurityContext 即安全上下文,其中存儲目前操作的使用者是誰、該使用者是否已經被認證、該使用者擁有哪些角色權限等資訊。

3、Authentication

public interface Authentication extends Principal, Serializable {
    Collection<? extends GrantedAuthority> getAuthorities();
​
    Object getCredentials();
​
    Object getDetails();
​
    Object getPrincipal();
​
    boolean isAuthenticated();
​
    void setAuthenticated(boolean var1) throws IllegalArgumentException;
}
複制代碼           
  • getAuthorities( ):擷取權限資訊清單。
  • getCredentials( ):擷取密碼資訊,使用者輸入的密碼字元串,在認證過後通常會被移除,保障使用者資訊安全。
  • getDetails( ):擷取細節資訊,web 應用中的實作接口通常為 WebAuthenticationDetails,它記錄了通路者的 ip 位址和 sessionId 的值。
  • getPrincipal( ):擷取身份資訊,大部分情況下傳回 UserDetails 接口的實作類,是架構中的常用接口之一。
  • isAuthenticated( ):判斷認證狀态,預設是 false,認證成功為 true。
  • setAuthenticated( ):設定是否進行認證。

4、AuthenticationManager

public interface AuthenticationManager {
  Authentication authenticate(Authentication var1) throws AuthenticationException;
}           

AuthenticationManager 接口其中隻有一個方法 authenticate(),該方法用于進行身份驗證,驗證通過則傳回帶有完整認證身份資訊的Authentication(包括 Authentication 中的所有屬性)。

2 Spring Security認證流程

淺析基于Spring Security 的身份認證流程

結合上面的時序圖,讓我們先熟悉下 Spring Security 的認證流程:

  1. 使用者進行認證,使用者名和密碼被 SecurityFilterChain 中的 UsernamePasswordAuthenticationFilter 過濾器攔截,并将請求封裝為 Authentication,其預設實作類是 UsernamePasswordAuthenticationToken。
  2. 将封裝的 UsernamePasswordAuthenticationToken 送出至 AuthenticationManager(認證管理器)進行認證。
  3. 認證成功後, AuthenticationManager(身份管理器)會傳回一個包含使用者身份資訊的 Authentication 執行個體(包括身份資訊,細節資訊,但密碼通常會被移除)。
  4. SecurityContextHolder (安全上下文容器)将認證成功的 Authentication 存儲到 SecurityContext(安全上下文)中。
其中,AuthenticationManager 接口是認證相關的核心接口,ProviderManager 是它的實作類。因為 Spring Security 支援多種認證方式,是以 ProviderManager 維護着一個List<AuthenticationProvider> 清單,包含多種認證方式,最終實際的認證工作就是由清單中的 AuthenticationProvider 完成的。其中最常見的 web 表單認證的對應的 AuthenticationProvider 實作類為 DaoAuthenticationProvider,它的内部又維護着一個 UserDetailsService負責擷取 UserDetails。最終 AuthenticationProvider 将 UserDetails 填充至 Authentication。

3 Spring Security認證的底層實作流程

淺析基于Spring Security 的身份認證流程

根據 Spring Security 的認證流程圖和源碼,接下來一起看下 Spring Security 如何完成使用者名密碼認證。

步驟1:當使用者發起認證請求,AbstractAuthenticationProcessingFilter 從 HttpServletRequest 建立 Authentication進行身份驗證。建立的身份驗證類型取決于 AbstractAuthenticationProcessingFilter 的子類。

3.1 UsernamePasswordAuthenticationFilter

以使用者名密碼認證為例 ,請求被 UsernamePasswordAuthenticationFilter 過濾器攔截,UsernamePasswordAuthenticationFilter 根據Request 中送出的使用者名和密碼建立一個 Token(UsernamePasswordAuthenticationToken)。

public class UsernamePasswordAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
    public static final String SPRING_SECURITY_FORM_USERNAME_KEY = "username";
    public static final String SPRING_SECURITY_FORM_PASSWORD_KEY = "password";
    private String usernameParameter = "username";
    private String passwordParameter = "password";
    private boolean postOnly = true;
​
    public UsernamePasswordAuthenticationFilter() {
      //比對url = "/login", method ="POST" 的請求
        super(new AntPathRequestMatcher("/login", "POST"));
    }
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        if (this.postOnly && !request.getMethod().equals("POST")) {
      //method 不是"POST" 抛異常
            throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
        } else {
      //擷取請求參數:使用者名、密碼
            String username = this.obtainUsername(request);
            String password = this.obtainPassword(request);
            if (username == null) {
                username = "";
            }
​
            if (password == null) {
                password = "";
            }
​
            username = username.trim();
        //先根據請求的使用者名和密碼構造一個未認證的Token
            UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
      //将請求資訊存儲到剛剛建立的UsernamePasswordAuthenticationToken中,其中包括ip、session等資訊。
            this.setDetails(request, authRequest);
      //核心:将token交給AuthenticationManager處理。
            return this.getAuthenticationManager().authenticate(authRequest);
        }
    }
  protected void setDetails(HttpServletRequest request, UsernamePasswordAuthenticationToken authRequest) {
        authRequest.setDetails(this.authenticationDetailsSource.buildDetails(request));
    }
}           

3.2 UsernamePasswordAuthenticationToken

UsernamePasswordAuthenticationToken 又是什麼呢?

public class UsernamePasswordAuthenticationToken extends AbstractAuthenticationToken {
    private static final long serialVersionUID = 500L;
    private final Object principal;
    private Object credentials;
  
  //構造方法,用于初始化一個未認證的Token
    public UsernamePasswordAuthenticationToken(Object principal, Object credentials) {
        super((Collection)null);
        this.principal = principal;
        this.credentials = credentials;
        this.setAuthenticated(false);
    }
  //構造方法,用于初始化一個已認證的Token
    public UsernamePasswordAuthenticationToken(Object principal, Object credentials, Collection<? extends GrantedAuthority> authorities) {
        super(authorities);
        this.principal = principal;
        this.credentials = credentials;
        super.setAuthenticated(true);
    }
​
    public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {
        //注意:如果直接調用該方法設定認證狀态未已認證會直接抛出異常,異常的提示很明确,無法将此令牌設定為受信任 - 使用采用 GrantedAuthority 清單的構造函數
    if (isAuthenticated) {
            throw new IllegalArgumentException("Cannot set this token to trusted - use constructor which takes a GrantedAuthority list instead");
        } else {
            super.setAuthenticated(false);
        }
    }
}           

從上面的源碼可以看出,其實 UsernamePasswordAuthenticationToken 的核心就是兩個構造方法,分别用于初始化未認證和認證的 Token。

步驟2:将生成的未認證 Token 交給 AuthenticationManager 進行身份認證。

淺析基于Spring Security 的身份認證流程

這一步是身份認證的核心,下面進行詳細講解:

  1. 未認證的 UsernamePasswordAuthenticationToken(攜帶使用者名、密碼資訊)被送出給 AuthenticationManager。AuthenticationManager 的實作類 ProviderManager 負責對認證請求鍊 AuthenticationProviders 進行管理。
  2. ProviderManager 通過循環的方式,發現 DaoAuthenticationProvider 的類型符合,使用 DaoAuthenticationProvider 進行認證。
  3. DaoAuthenticationProvider 從 UserDetailsService 中查找 UserDetails。
  4. DaoAuthenticationProvider 使用 PasswordEncoder 驗證上一步傳回的 UserDetails 中的使用者密碼。
  5. 當身份驗證成功, Authentication 傳回一個已認證的 UsernamePasswordAuthenticationToken ,其中包含 UserDetailsService 傳回的 UserDetails 資訊。最終,認證成功的 UsernamePasswordAuthenticationToken 添加到 SecurityContextHolder 完成賬号密碼的身份認證。

3.3 AuthenticationManager

AuthenticationManager 作為一個最上級的接口本身不包含驗證的邏輯,隻有一個 authenticate 方法用于處理身份驗證請求,傳回一個 Authentication 對象。它的作用隻是用來管理 AuthenticationProvider。

public interface AuthenticationManager {
    Authentication authenticate(Authentication var1) throws AuthenticationException;
}           

AuthenticationManager 的實作方式有很多,通常是使用 ProviderManager 對認證請求鍊進行管理; 這裡可以看一下 ProviderManager 源碼中 authenticate 方法的注解。

* The list of {@link AuthenticationProvider}s will be successively tried until an <code>AuthenticationProvider</code> indicates it is capable of authenticating the type of <code>Authentication</code> object passed. 
Authentication will then be attempted with that <code>AuthenticationProvider</code>.  
* If more than one <code>AuthenticationProvider</code> supports the passed <code>Authentication</code> object, the first one able to successfully authenticate the <code>Authentication</code> object determines the <code>result</code>,  overriding any possible <code>AuthenticationException</code> thrown by earlier supporting <code>AuthenticationProvider</code>s.
On successful authentication, no subsequent <code>AuthenticationProvider</code>s will be tried.
* If authentication was not successful by any supporting <code>AuthenticationProvider</code> the last thrown <code>AuthenticationException</code> will be rethrown.
複制代碼           
翻譯如下: AuthenticationProvider 清單将被鍊式連續嘗試執行,直到一個 AuthenticationProvider 能夠驗證通過,即可結束。 如果任何一個能夠成功驗證 Authentication 對象确定結果,就會忽略之前 AuthenticationProvider 認證失敗抛出的異常以及 null 響應。成功認證後,不會嘗試後續的 AuthenticationProviders。 如果任何支援 AuthenticationProvider 的身份驗證未成功,則最後抛出的 AuthenticationException 将被重新抛出。

簡單點說,List 中隻要有一個 AuthenticationProvider 能夠驗證成功,不管之前是否有失敗的情況,結果都會傳回這次成功的結果,之前抛出的異常也會被忽略。

3.4 ProviderManager

這裡看一下ProviderManage r的源碼,它是通過 for 循環的方式擷取 AuthenticationProvider 對象。

public Authentication authenticate(Authentication authentication) throws AuthenticationException {
    Class<? extends Authentication> toTest = authentication.getClass();
    AuthenticationException lastException = null;
    AuthenticationException parentException = null;
    Authentication result = null;
    Authentication parentResult = null;
    int currentPosition = 0;
    int size = this.providers.size();
    //這裡循環周遊AuthenticationProvider
    for (AuthenticationProvider provider : getProviders()) {
      // 判斷是否支援處理
      if (!provider.supports(toTest)) {
        continue;
      }
      if (logger.isTraceEnabled()) {
        logger.trace(LogMessage.format("Authenticating request with %s (%d/%d)",
            provider.getClass().getSimpleName(), ++currentPosition, size));
      }
      try {
        //調用實作類的authenticate方法進行真實業務邏輯認證處理
        result = provider.authenticate(authentication);
        if (result != null) {
          copyDetails(authentication, result);
          break;
        }
      }
      catch (AccountStatusException | InternalAuthenticationServiceException ex) {
        prepareException(ex, authentication);
        throw ex;
      }
      catch (AuthenticationException ex) {
        lastException = ex;
      }
    }
  }           

authenticate 這個方法是在 ProviderManager 類上的,這個類實作了 AuthenticationManager 接口,通過 for 循環的方式去擷取所有的 AuthenticationProvider,而真正校驗的邏輯是寫在 AuthenticationProvider 中的。AuthenticationManager 其實隻是将 AuthenticationProvider 收集起來,然後在登入時逐個進入 AuthenticationProvider 并判斷此種驗證邏輯是否支援本次登入方式,根據傳進來的 Authentication 類型挑選出符合的 provider 進行校驗處理。

3.5 AuthenticationProvider

public interface AuthenticationProvider{
      Authentication authenticate(Authentication authentication) throws AuthenticationException;
​
      boolean supports(Class<?> authentication);
}           

Spring Security 進行身份認證的方式有許多種,除了最常見的賬号密碼認證、手機号認證,還有 ReremberMe 認證、三方認證等,它們均遵守着由 AuthenticationProvider 來處理認證請求的規則。

可以說到目前為止,還沒有進行”真正的“賬号密碼校驗。有些地方說是在預設的 DaoAuthenticationProvider 中判斷Authentication 類型進行判斷直接完成的。其實并不是很準确,應該說 AuthenticationProvider 的認證其實由 AbstractUserDetailsAuthenticationProvider 抽象類和 AbstractUserDetailsAuthenticationProvider 的子類 DaoAuthenticationProvider共同協助完成的。

3.6 AbstractUserDetailsAuthenticationProvider

public abstract class AbstractUserDetailsAuthenticationProvider
        implements AuthenticationProvider, InitializingBean, MessageSourceAware {
 
    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        //斷言入參Authentication如果不是UsernamePasswordAuthenticationToken類型,直接抛異常。
        Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication,
                () -> this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.onlySupports",
                        "Only UsernamePasswordAuthenticationToken is supported"));
        //拿到使用者名
        String username = determineUsername(authentication);
        boolean cacheWasUsed = true;
        //通過使用者名從緩存中擷取UserDetails
        UserDetails user = this.userCache.getUserFromCache(username);
        //若緩存中沒有,從子類DaoAuthenticationProvider中擷取
        if (user == null) {
            cacheWasUsed = false;
            try {
                // 子類DaoAuthenticationProvider實作
                user = retrieveUser(username, (UsernamePasswordAuthenticationToken) authentication);
            }
            catch (UsernameNotFoundException ex) {
                this.logger.debug("Failed to find user '" + username + "'");
                if (!this.hideUserNotFoundExceptions) {
                    throw ex;
                }
                throw new BadCredentialsException(this.messages
                        .getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
            }
            Assert.notNull(user, "retrieveUser returned null - a violation of the interface contract");
        }
        try {
            //前置檢查:檢查目前使用者是否鎖定、過期或當機
            this.preAuthenticationChecks.check(user);
            additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken) authentication);
        }
        catch (AuthenticationException ex) {
            if (!cacheWasUsed) {
                throw ex;
            }
            cacheWasUsed = false;
            user = retrieveUser(username, (UsernamePasswordAuthenticationToken) authentication);
            this.preAuthenticationChecks.check(user);
            additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken) authentication);
        }
        //後置檢查:檢查使用者的憑據/密碼是否過期
        this.postAuthenticationChecks.check(user);
        if (!cacheWasUsed) {
        //如果使用者資訊沒有被緩存,放入緩存
            this.userCache.putUserInCache(user);
        }
        Object principalToReturn = user;
        if (this.forcePrincipalAsString) {
            principalToReturn = user.getUsername();
        }
        //傳回通過驗證的使用者資訊,封裝成了UsernamePasswordAuthenticationToken
        return createSuccessAuthentication(principalToReturn, authentication, user);
    }           

通過上面的源碼,和标注的注釋。可以清晰的了解其驗證流程:

  1. 首先擷取使用者名 username;然後嘗試從緩存中擷取 User;若緩存中沒有,再調用 retrieveUser 方擷取(子類DaoAuthenticationProvider 實作)。
  2. 然後,進行前置身份認證檢查(校驗賬号是否鎖定、是否可用、是否過期)和額外的檢查(憑據/密碼校驗)。進行後置身份認證檢查(校驗憑據/密碼是否過期)
  3. 若目前使用者還沒有被緩存的話,将使用者存入緩存;最後,建立身份認證成功的 Authentication。

3.7 DaoAuthenticationProvider

DaoAuthenticationProvider 是 AuthenticationProvider 最常用的實作,其作用在注釋中已經清楚的表明。

* An {@link AuthenticationProvider} implementation that retrieves user details from a
* {@link UserDetailsService}.           

即通過 UsernamePasswordAuthenticationToken 擷取到 username,然後通過 UserDetailsService 擷取使用者詳細資訊。

下面看一下 DaoAuthenticationProvider 是如何重寫的 retrieveUser 和 additionalAuthenticationChecks 兩個方法。(部分源碼省略)

protected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
    this.prepareTimingAttackProtection();
    try {
       //通過UserDetailsService擷取UserDetails資訊。(從自定義的UserDetailsService擷取)
        UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);
        if (loadedUser == null) {
            throw new InternalAuthenticationServiceException("UserDetailsService returned null, which is an interface contract violation");
        } else {
            return loadedUser;
            }
    }
}
protected void additionalAuthenticationChecks(UserDetails userDetails,
      UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
   if (authentication.getCredentials() == null) {
      this.logger.debug("Failed to authenticate since no credentials provided");
      throw new BadCredentialsException(this.messages
            .getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
   }
   String presentedPassword = authentication.getCredentials().toString();
    //驗證密碼是否正确
   if (!this.passwordEncoder.matches(presentedPassword, userDetails.getPassword())) {
      this.logger.debug("Failed to authenticate since password does not match stored value");
      throw new BadCredentialsException(this.messages
            .getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
   }
}           

可以看出,主要是利用 UserDetailsService 和 PasswordEncoder 來實作擷取使用者資訊和密碼認證。

至此身份認證的源碼分析就結束了,在 spring security 的整個認證流程中其實最難了解的就是 AuthenticationManager 、ProviderManager、AuthenticationProvider 之間的關系,其關系如下圖所示。

淺析基于Spring Security 的身份認證流程

不同的認證方式對應不同的 AuthenticationProvider,一個完整的認證流程可由多個 AuthenticationProvider 來達成。在實際使用中,很多特定場景需要我們自定義 AuthenticationProvider 來進行認證。

步驟3:認證成功。

将認證資訊存儲到 SecurityContextHodler 并調用 RememberMeServices(如有開啟)等,回調 AuthenticationSuccessHandler 進行處理。

步驟4:認證失敗。

将認證資訊存儲到 SecurityContextHodler 并調用 RememberMeServices(如有開啟)等,并回調 AuthenticationSuccessHandler 處理。

核心元件的調用流程:

下圖即總結的核心元件的調用流程。在實際運用或問題排查過程中,我們均可以參照下面的流程進行學習和排查。

淺析基于Spring Security 的身份認證流程

4 總結

本文僅針對 Spring Security 的認證流程進行了詳細講解,但Spring Security 登入相關的内容遠不止這些。如果大家有更好的想法,歡迎多多交流,一起學習,一起進步!