Spring Security認證流程類圖
核心驗證器
AuthenticationManager
提供了認證方法的入口,接收一個Authentiaton對象作爲參數
ProviderManager
AuthenticationManager
的一個實現類
提供了基本的認證邏輯和方法
它包含了一個List<AuthenticationProvider>
對象
通過 AuthenticationProvider
接口來擴展出不同的認證提供者(當Spring Security默認提供的實現類不能滿足需求的時候可以擴展AuthenticationProvider 覆蓋supports(Class<?> authentication) 方法)
驗證邏輯
-
AuthenticationManager
接收Authentication
對象作爲參數,並通過authenticate(Authentication)
方法對之驗證 -
AuthenticationProvider
實現類用來支撐對Authentication
對象的驗證動作 -
UsernamePasswordAuthenticationToken
實現了Authentication
主要是將用戶輸入的用戶名和密碼進行封裝,並供給AuthenticationManager
進行驗證 驗證完成以後將返回一個認證成功的 Authentication 對象
Authentication
Authentication接口中的主要方法
public interface Authentication extends Principal, Serializable { // 權限集合,可使用AuthorityUtils.commaSeparatedStringToAuthorityList("admin,ROLE_ADMIN")返回字符串權限集合 Collection<? extends GrantedAuthority> getAuthorities(); // 用戶名密碼認證時可以理解爲密碼 Object getCredentials(); // 認證請求包含的一些附加信息(如 IP 地址,數字證書號) Object getDetails(); // 用戶名密碼認證時可理解爲用戶名 Object getPrincipal(); // 是否被認證 boolean isAuthenticated(); // 設置是否能被認證 void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException;
ProviderManager
AuthenticationManager
的實現類,提供了基本認證實現邏輯和流程
public Authentication authenticate(Authentication authentication) throws AuthenticationException { // 1.獲取當前的Authentication的認證類型 Class<? extends Authentication> toTest = authentication.getClass(); AuthenticationException lastException = null; Authentication result = null; boolean debug = logger.isDebugEnabled(); // 2.遍歷所有的 providers 使用 supports 方法判斷該 provider 是否支持當前的認證類型 for (AuthenticationProvider provider : getProviders()) { if (!provider.supports(toTest)) { continue; } if (debug) { logger.debug("Authentication attempt using " + provider.getClass().getName()); } try { // 3.若支持,調用 provider#authenticat 認證 result = provider.authenticate(authentication); if (result != null) { // 4.認證通過則重新生成 Authentication 對應的 Token copyDetails(authentication, result); break; } } catch (AccountStatusException e) { prepareException(e, authentication); // SEC-546: Avoid polling additional providers if auth failure is due to // invalid account status throw e; } catch (InternalAuthenticationServiceException e) { prepareException(e, authentication); throw e; } catch (AuthenticationException e) { lastException = e; } } if (result == null && parent != null) { // Allow the parent to try. try { // 5.如果 1 沒有驗證通過,則使用父類 AuthenticationManager 進行驗證 result = parent.authenticate(authentication); } catch (ProviderNotFoundException e) { // ignore as we will throw below if no other exception occurred prior to // calling parent and the parent // may throw ProviderNotFound even though a provider in the child already // handled the request } catch (AuthenticationException e) { lastException = e; } } // 6. 是否查出敏感信息 if (result != null) { if (eraseCredentialsAfterAuthentication && (result instanceof CredentialsContainer)) { // Authentication is complete. Remove credentials and other secret data // from authentication ((CredentialsContainer) result).eraseCredentials(); } eventPublisher.publishAuthenticationSuccess(result); return result; } // Parent was null, or didn't authenticate (or throw an exception). if (lastException == null) { lastException = new ProviderNotFoundException(messages.getMessage( "ProviderManager.providerNotFound", new Object[] { toTest.getName() }, "No AuthenticationProvider found for {0}")); } prepareException(lastException, authentication); throw lastException; }
- 遍歷所有的 Providers,然後依次執行該 Provider 的驗證方法
- 如果某一個 Provider 驗證成功,跳出循環不再執行後續的驗證
- 如果驗證成功,會將返回的 result 即 Authentication 對象進一步封裝爲 Authentication Token,比如 UsernamePasswordAuthenticationToken、RememberMeAuthenticationToken 等 這些 Authentication Token 也都繼承自 Authentication 對象
- 如果 1 沒有任何一個 Provider 驗證成功,則試圖使用其 parent Authentication Manager 進行驗證
- 是否需要擦除密碼等敏感信息
AuthenticationProvider
AuthenticationProvider
本身也就是一個接口
它的實現類AbstractUserDetailsAuthenticationProvider
和AbstractUserDetailsAuthenticationProvider
的子類DaoAuthenticationProvider
是Spring Security中一個核心的Provider,對所有的數據庫提供了基本方法和入口
DaoAuthenticationProvider
主要做了以下事情
對用戶身份進行加密
1.可直接返回BCryptPasswordEncoder 也可自己實現該接口使用自己的加密算法
實現了 AbstractUserDetailsAuthenticationProvider 兩個抽象方法
獲取用戶信息的擴展點
實現 additionalAuthenticationChecks 的驗證方法(主要驗證密碼)
AbstractUserDetailsAuthenticationProvider
AbstractUserDetailsAuthenticationProvider爲DaoAuthenticationProvider提供了基本的認證方法
public Authentication authenticate(Authentication authentication) throws AuthenticationException { Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication, messages.getMessage( "AbstractUserDetailsAuthenticationProvider.onlySupports", "Only UsernamePasswordAuthenticationToken is supported")); // Determine username String username = (authentication.getPrincipal() == null) ? "NONE_PROVIDED" : authentication.getName(); boolean cacheWasUsed = true; UserDetails user = this.userCache.getUserFromCache(username); if (user == null) { cacheWasUsed = false; try { #1.獲取用戶信息由子類實現即DaoAuthenticationProvider user = retrieveUser(username, (UsernamePasswordAuthenticationToken) authentication); } catch (UsernameNotFoundException notFound) { logger.debug("User '" + username + "' not found"); if (hideUserNotFoundExceptions) { throw new BadCredentialsException(messages.getMessage( "AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials")); } else { throw notFound; } } Assert.notNull(user, "retrieveUser returned null - a violation of the interface contract"); } try { #2.前檢查由DefaultPreAuthenticationChecks類實現(主要判斷當前用戶是否鎖定,過期,凍結User接口) preAuthenticationChecks.check(user); #3.子類實現 additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken) authentication); } catch (AuthenticationException exception) { if (cacheWasUsed) { // There was a problem, so try again after checking // we're using latest data (i.e. not from the cache) cacheWasUsed = false; user = retrieveUser(username, (UsernamePasswordAuthenticationToken) authentication); preAuthenticationChecks.check(user); additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken) authentication); } else { throw exception; } } #4.檢測用戶密碼是否過期對應#2 的User接口 postAuthenticationChecks.check(user); if (!cacheWasUsed) { this.userCache.putUserInCache(user); } Object principalToReturn = user; if (forcePrincipalAsString) { principalToReturn = user.getUsername(); } return createSuccessAuthentication(principalToReturn, authentication, user); }
AbstractUserDetailsAuthenticationProvider主要實現了AuthenticationProvider的接口方法 authenticate 並提供了相關的驗證邏輯;
獲取用戶返回UserDetails AbstractUserDetailsAuthenticationProvider定義了一個抽象的方法