AuthenticationManager、ProviderManager


本篇主要講述以下幾點:

1、AuthenticationManager、ProviderManager和AuthenticationProvider三者之間的關系

2、以UsernamePasswordAuthenticationFilter為例,如何使用AuthenticationProvider的子類AbstractUserDetailsAuthenticationProvider、

   DaoAuthenticationProvider來驗證用戶名密碼

3、Authentication、UserDetails的內部結構

 

先來看一張時序圖:

從上圖可以看出驗證邏輯為:

1、在UsernamePasswordAuthenticationFilter的attemptAuthentication()方法中,調用AuthenticationManager進行認證

2、AuthenticationManager接收Authentication對象作為參數,並通過authenticate方法對其進行驗證(實際由其實現類ProviderManager完成)

3、在ProviderManagerauthenticate方法中,輪訓成員變量List<AuthenticationProvider> providers。該providers中如果有一個

      AuthenticationProvider的supports函數返回true,那么就會調用該AuthenticationProvider的authenticate函數認證,如果認證成功則整個

      認證過程結束。如果不成功,則繼續使用下一個合適的AuthenticationProvider進行認證,只要有一個認證成功則為認證成功。

4、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

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);
    }

1、前檢查和后檢查的參數為UserDetails,正好對應UserDetails中的4個isXXX方法

2、retrieveUser()和additionalAuthenticationChecks()由子類DaoAuthenticationProvider實現

3、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認證配置(一)

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM