Spring Security實戰-認證核心驗證器驗證邏輯AuthenticationProviderManagerAuthenticationProvider

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本身也就是一個接口 它的實現類AbstractUserDetailsAuthenticationProviderAbstractUserDetailsAuthenticationProvider的子類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定義了一個抽象的方法

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章