文章目錄
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()
對象,裡面有兩個屬性,remoteAddr 和 sessionId。WebAuthenticationDetails
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();