本篇主要講述如下幾點:html
一、AuthenticationManager、ProviderManager和AuthenticationProvider三者之間的關係緩存
二、以UsernamePasswordAuthenticationFilter爲例,如何使用AuthenticationProvider的子類AbstractUserDetailsAuthenticationProvider、安全
DaoAuthenticationProvider來驗證用戶名密碼session
三、Authentication、UserDetails的內部結構app
先來看一張時序圖:ide
從上圖能夠看出驗證邏輯爲:函數
一、在UsernamePasswordAuthenticationFilter的attemptAuthentication()方法中,調用AuthenticationManager進行認證post
二、AuthenticationManager接收Authentication對象做爲參數,並經過authenticate方法對其進行驗證(實際由其實現類ProviderManager完成)this
三、在ProviderManager的authenticate方法中,輪訓成員變量List<AuthenticationProvider> providers。該providers中若是有一個spa
AuthenticationProvider的supports函數返回true,那麼就會調用該AuthenticationProvider的authenticate函數認證,若是認證成功則整個
認證過程結束。若是不成功,則繼續使用下一個合適的AuthenticationProvider進行認證,只要有一個認證成功則爲認證成功。
四、UsernamePasswordAuthenticationToken實現了Authentication,主要是將用戶輸入的用戶名密碼進行封裝,並提供給
AuthenticationManager進行驗證,驗證成功後,返回一個認證成功的UsernamePasswordAuthenticationToken對象
AuthenticationManager
AuthenticationManager是一個接口,是認證方法的入口,接收一個Authentication對象做爲參數
public interface AuthenticationManager { Authentication authenticate(Authentication authentication) throws AuthenticationException; }
ProviderManager
它是AuthenticationManager的一個實現類,實現了authenticate(Authentication authentication)方法,還有一個成員變量
List<AuthenticationProvider> providers
public class ProviderManager implements AuthenticationManager, MessageSourceAware, InitializingBean { ...... private List<AuthenticationProvider> providers = Collections.emptyList(); public Authentication authenticate(Authentication authentication) throws AuthenticationException { ...... } }
AuthenticationProvider
AuthenticationProvider也是一個接口,包含兩個函數authenticate和supports。當Spring Security默認提供的Provider不能知足需求的時候,能夠經過實現AuthenticationProvider接口來擴展出不一樣的認證提供者
public interface AuthenticationProvider { //經過參數Authentication對象,進行認證 Authentication authenticate(Authentication authentication) throws AuthenticationException; //是否支持該認證類型 boolean supports(Class<?> authentication); }
Authentication是一個接口,經過該接口能夠得到用戶相關信息、安全實體的標識以及認證請求的上下文信息等
在Spring Security中,有不少Authentication的實現類。如UsernamePasswordAuthenticationToken、AnonymousAuthenticationToken和
RememberMeAuthenticationToken等等
一般不會被擴展,除非是爲了支持某種特定類型的認證
public interface Authentication extends Principal, Serializable { //權限結合,可以使用AuthorityUtils.commaSeparatedStringToAuthorityList("admin, ROLE_ADMIN")返回字符串權限集合 Collection<? extends GrantedAuthority> getAuthorities(); //用戶名密碼認證時能夠理解爲密碼 Object getCredentials(); //認證時包含的一些信息。如remoteAddress、sessionId Object getDetails(); //用戶名密碼認證時可理解時用戶名 Object getPrincipal(); //是否被認證,認證爲true boolean isAuthenticated(); //設置是否被認證 void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException; }
UserDetails
UserDetails也是一個接口,主要封裝用戶名密碼是否過時、是否可用等信息
public interface UserDetails extends Serializable { //權限集合 Collection<? extends GrantedAuthority> getAuthorities(); //密碼 String getPassword(); //用戶名 String getUsername(); //用戶名是否沒有過時 boolean isAccountNonExpired(); //用戶名是否沒有鎖定 boolean isAccountNonLocked(); //用戶密碼是否沒有過時 boolean isCredentialsNonExpired(); //帳號是否可用(可理解爲是否刪除) boolean isEnabled(); }
接下來看具體的實現方法:
ProviderManager
public Authentication authenticate(Authentication authentication) throws AuthenticationException { //獲取當前的Authentication的認證類型 Class<? extends Authentication> toTest = authentication.getClass(); AuthenticationException lastException = null; Authentication result = null; boolean debug = logger.isDebugEnabled(); //遍歷全部的providers for (AuthenticationProvider provider : getProviders()) { //判斷該provider是否支持當前的認證類型。不支持,遍歷下一個 if (!provider.supports(toTest)) { continue; } if (debug) { logger.debug("Authentication attempt using " + provider.getClass().getName()); } try { //調用provider的authenticat方法認證 result = provider.authenticate(authentication); if (result != null) { //認證經過的話,將認證結果的details賦值到當前認證對象authentication。而後跳出循環 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; } } ...... }
AbstractUserDetailsAuthenticationProvider
AbstractUserDetailsAuthenticationProvider是 AuthenticationProvider 的核心實現類
public Authentication authenticate(Authentication authentication) throws AuthenticationException { //若是authentication不是UsernamePasswordAuthenticationToken類型,則拋出異常 Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication, messages.getMessage( "AbstractUserDetailsAuthenticationProvider.onlySupports", "Only UsernamePasswordAuthenticationToken is supported")); // 獲取用戶名 String username = (authentication.getPrincipal() == null) ? "NONE_PROVIDED" : authentication.getName(); //從緩存中獲取UserDetails boolean cacheWasUsed = true; UserDetails user = this.userCache.getUserFromCache(username); //緩存中沒有,則從子類DaoAuthenticationProvider中獲取 if (user == null) { cacheWasUsed = false; try { //獲取用戶信息。由子類DaoAuthenticationProvider實現 user = retrieveUser(username, (UsernamePasswordAuthenticationToken) authentication); } ...... } try { //前檢查。由DefaultPreAuthenticationChecks實現(主要判斷當前用戶是否鎖定,過時,凍結User) preAuthenticationChecks.check(user); //附加檢查。由子類DaoAuthenticationProvider實現 additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken) authentication); } catch (AuthenticationException exception) { ...... } //後檢查。由DefaultPostAuthenticationChecks實現(檢測密碼是否過時) postAuthenticationChecks.check(user); if (!cacheWasUsed) { this.userCache.putUserInCache(user); } Object principalToReturn = user; if (forcePrincipalAsString) { principalToReturn = user.getUsername(); } //將已經過驗證的用戶信息封裝成 UsernamePasswordAuthenticationToken 對象並返回 return createSuccessAuthentication(principalToReturn, authentication, user); }
一、前檢查和後檢查的參數爲UserDetails,正好對應UserDetails中的4個isXXX方法
二、retrieveUser()和additionalAuthenticationChecks()由子類DaoAuthenticationProvider實現
三、createSuccessAuthentication以下:
protected Authentication createSuccessAuthentication(Object principal, Authentication authentication, UserDetails user) { //從新封裝成UsernamePasswordAuthenticationToken。包含用戶名、密碼,以及對應的權限 //該構造方法會給父類Authentication賦值: super.setAuthenticated(true) UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken( principal, authentication.getCredentials(), authoritiesMapper.mapAuthorities(user.getAuthorities())); result.setDetails(authentication.getDetails()); return result; }
DaoAuthenticationProvider
DaoAuthenticationProvider實現了父類的retrieveUser()和additionalAuthenticationChecks()方法
protected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException { UserDetails loadedUser; try { //調用UserDetailsService接口的loadUserByUsername獲取用戶信息 //經過實現UserDetailsService接口來擴展對用戶密碼的校驗 loadedUser = this.getUserDetailsService().loadUserByUsername(username); } ...... //若是找不到該用戶,則拋出異常 if (loadedUser == null) { throw new InternalAuthenticationServiceException( "UserDetailsService returned null, which is an interface contract violation"); } return loadedUser; }
@SuppressWarnings("deprecation") protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException { Object salt = null; if (this.saltSource != null) { salt = this.saltSource.getSalt(userDetails); } //密碼爲空,則直接拋出異常 if (authentication.getCredentials() == null) { logger.debug("Authentication failed: no credentials provided"); throw new BadCredentialsException(messages.getMessage( "AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials")); } //獲取用戶輸入的密碼 String presentedPassword = authentication.getCredentials().toString(); //將緩存中的密碼(也多是自定義查詢的密碼)與用戶輸入密碼匹配 //若是匹配不上,則拋出異常 if (!passwordEncoder.isPasswordValid(userDetails.getPassword(), presentedPassword, salt)) { logger.debug("Authentication failed: password does not match stored value"); throw new BadCredentialsException(messages.getMessage( "AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials")); } }
關於UserDetailsService.loadUserByUsername方法,可參考Spring Security認證配置(一)