天天看點

Spring Security 認證流程

文章目錄

Spring Security 登入認證流程

    • 1. UsernamePasswordAuthenticationFilter#attempt
    • 2. UsernamePasswordAuthenticationToken^③^
    • 3. AuthenticationManager#authenticate^④^
    • 4. ProviderManager#authenticate
    • 5. AbstractUserDetailsAuthenticationProvider#authenticate
    • 6. DaoAuthenticationProvider#retrieveUser
    • 7. UserDetailsService#loadUserByUsername
    • 8.AbstractAuthenticationProcessingFilter#doFilter
    • 9. Spring Security 認證流程圖

Spring Security 登入認證流程

登入的整體流程是:使用者點選登入從前端發送請求,後端接受前端發送來的使用者名和密碼,然後從資料庫中查詢是否存在該使用者;如果存在,則放行,讓使用者進入系統;如果不存在或者使用者名、密碼錯誤,則提示錯誤資訊。在 Spring Security中,大緻遵循這個流程,隻不過在這個過程中做了很多額外的校驗工作。

1. UsernamePasswordAuthenticationFilter#attempt

使用者發送登入請求,由 

AbstractAuthenticationProcessingFilter#doFilter

 處理,在該方法中調用其子類 

UsernamePasswordAuthenticationFilter

 的 

attempt

 方法進行處理并傳回 

Authentication

 對象。

public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
    // 1.判斷請求方法是否為POST
    if (this.postOnly && !request.getMethod().equals("POST")) {
        throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
    } else {
        // 2.擷取username和password(通過 request.getParameter("username") 擷取)
        String username = this.obtainUsername(request);
        username = username != null ? username : "";
        username = username.trim();
        String password = this.obtainPassword(request);
        password = password != null ? password : "";
        // ③
        UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
        this.setDetails(request, authRequest);
        // ④
        return this.getAuthenticationManager().authenticate(authRequest);
    }
}
           

2. UsernamePasswordAuthenticationToken③

擷取到請求中傳遞過來的使用者名和密碼後,構造一個 

UsernamePasswordAuthenticationToken

 對象,将

username

 和 

password

 傳入,

username

 對應 

principal

, 

password

 對應 

credentials

public UsernamePasswordAuthenticationToken(Object principal, Object credentials) {
    super((Collection)null);
    this.principal = principal;
    this.credentials = credentials;
    // false:使用者未認證
    this.setAuthenticated(false);
}

public UsernamePasswordAuthenticationToken(Object principal, Object credentials, Collection<? extends GrantedAuthority> authorities) {
    super(authorities);
    this.principal = principal;
    this.credentials = credentials;
    // true:使用者已認證
    super.setAuthenticated(true);
}
           

setDeatils()

 實際是調用 

WebAuthenticationDetails#buildDetails

 來擷取 

remoteAddr

 和 

sessionId

 并将其傳回到 

AbstractAuthenticationToken.details

protected void setDetails(HttpServletRequest request, UsernamePasswordAuthenticationToken authRequest) {
    // authenticationDetailsSource = new WebAuthenticationDetailsSource();
    authRequest.setDetails(this.authenticationDetailsSource.buildDetails(request));
}

// 實際調用的是 WebAuthenticationDetails#buildDetails
public WebAuthenticationDetails buildDetails(HttpServletRequest context) {
    return new WebAuthenticationDetails(context);
}

public WebAuthenticationDetails(HttpServletRequest request) {
    this.remoteAddress = request.getRemoteAddr();
    HttpSession session = request.getSession(false);
    this.sessionId = session != null ? session.getId() : null;
}
           

UsernamePasswordAuthenticationToken 總結

UsernamePasswordAuthenticationToken

 繼承 

AbstractAuthenticationToken

,是一個放置認證對象資訊的類,擁有的屬性分别是:

  • principal

    :對應使用者名;
  • credentials

    :對應密碼;
  • authenticated

    :是否已認證;
  • authorities

    :權限集合;
  • details

    :其他細節,
  • setDetails()

     後變成一個 

    WebAuthenticationDetails

     對象,裡面有兩個屬性,remoteAddr 和 sessionId。

3. AuthenticationManager#authenticate④

在建構 

UsernamePasswordAuthenticationToken

 對象後,執行 

AuthenticationManager

 的 

authenticae

 方法。

AuthenticationManager

 接口中隻有一個 

authenticae

 方法。該接口的實作類 

ProviderManager

 中有 

authenticate

 方法。

ProviderManager

 内部維護一個List表,存放多種認證邏輯(使用者名+密碼,郵箱+密碼等等),不同的認證邏輯對應不同的 

AuthenticationProvider

4. ProviderManager#authenticate

public Authentication authenticate(Authentication authentication) {
    Class<? extends Authentication> toTest = authentication.getClass();
    AuthenticationException lastException = null;
    AuthenticationException parentException = null;
    Authentication result = null;
    Authentication parentResult = null;

    // 1.擷取傳入的 Authentication,判斷目前 provider 是否 support 該 Authentication。
    for (AuthenticationProvider provider : getProviders()) {
        if (!provider.supports(toTest)) {
            continue;
        }
        try {
            // 2.如果支援,則調用 provider的authenticate方法 進行校驗
            // 校驗完成後會傳回一個新的Authentication。
            result = provider.authenticate(authentication);
            ...
        }
    }
    
    // 3.provider有多個,如果 provider的authenticate方法 未能正常傳回一個 Authentication,則調用 provider的parent的authenticate方法 繼續校驗。
    if (result == null && parent != null) {
        try {
            result = parentResult = parent.authenticate(authentication);
        }
        ...
    }
    ...
}
           

周遊所有 

AuthenticationProvider

 并通過 

supports

 方法判斷其是否支援傳入的 

Authentication

。找到支援 

UsernamePasswordAuthenticationToken

 的 

provider

,即

DaoAuthenticationProvider

,進入該類的 

authenticate

 方法,但該類中并未重寫 

authenticate

 方法,于是來到其父類 

AbstractUserDetailsAuthenticationProvider

 的 

authenticate

 方法。

5. AbstractUserDetailsAuthenticationProvider#authenticate

  • 從 

    Authentication

     中擷取登入的使用者名。通過使用者名調用 

    retrieveUser()

     方法擷取 

    UserDetails

  • 在擷取到 

    UserDetails

     之後執行以下驗證方法:
    • preAuthenticationChecks.check

       方法:驗證使用者中的各狀态屬性是否正常,例如:是否被禁用、是否被鎖定等。
    • additionalAuthenticationChecks.check

       方法:密碼比對。
    • post AuthenticationChecks.check

       方法:檢查密碼是否過期。
    • createSuccessAuthentication

       方法:建構一個新的 

      UsernamePasswordAuthenticationToken

       傳回到 

      UsernamePasswordAuthenticationFilter

       中 。

6. DaoAuthenticationProvider#retrieveUser

調用 UserDetailsService#loaUserByUsername 去資料庫中讀取使用者資訊,将其封裝并傳回 UserDetails。

7. UserDetailsService#loadUserByUsername

編寫 

XXXUserDetails

 實作類實作 

UserDetails

 接口,在這個實作類中重寫 

loadUserByUsername

 方法從資料庫中查詢資料;最後傳回一個 

UserDetails

 對象,傳回到 

AbstractUserDetailsAuthenticationProvider

 執行一系列驗證方法。

8.AbstractAuthenticationProcessingFilter#doFilter

通過上述描述可知:使用者發送登入請求,由 

AbstractAuthenticationProcessingFilter#doFilter

 處理,在該方法中調用其子類 

UsernamePasswordAuthenticationFilter

 的 

attempt

 方法進行處理并傳回 

Authentication

 對象(

AbstractUserDetailsAuthenticationProvider#createSuccessAuthentication

)。

最後,根據認證的成功或者失敗調用相應的 handler

successfulAuthentication

:該方法就是将認證資訊存儲到 Session 中。

successfulAuthentication

:該方法就是将認證資訊存儲到 Session 中。

SecurityContextHolder.getContext().setAuthentication(authResult);
           

unsuccessfulAuthentication 

SecurityContextHolder.clearContext();
           

9. Spring Security 認證流程圖

Spring Security 認證流程