Spring Security解析九:AuthenticationManager

1,簡要說明

在Spring Security中對用戶進行認證的是AuthenticationManager,其只有一個方法,嘗試對封裝了認證信息的Authentication進行身份驗證,如果成功,則返回完全填充的Authentication(包括授予的權限)。

public interface AuthenticationManager {
    /**
     * 嘗試對通過Authentication實例對象封裝的身份信息進行驗證。
     * 如果驗證成功,則返回完全填充的Authentication對象(包括授予的權限)。
     *
     * AuthenticationManager 建議遵循以下的約定
     * 1,如果帳戶被禁用并且AuthenticationManager可以測試此狀態,則必須引發 DisabledException
     * 2,如果帳戶被鎖定并且并且AuthenticationManager可以測試帳戶鎖定,則必須拋出LockedException
     * 3,如果憑據不正確,則必須拋出BadCredentialsException
     * 雖然上述選項是可選的,但是 AuthenticationManager 必須始終測試憑據。
     * 我們應該上述順序捕獲這些異常,同時實現者也應按上述順序拋出異常(即,如果帳戶被禁用或鎖定,
     * 則立即拒絕身份驗證請求,并且不執行憑據測試過程),這可以防止根據禁用或鎖定的帳戶測試憑據。
     */
    Authentication authenticate(Authentication authentication)
            throws AuthenticationException;
}

AuthenticationManager 只關注認證成功與否而并不關心具體的認證方式。例如我們可以通過用戶名及密碼、短信、刷臉、OAuth2協議等方式進行認證。對于這些具體認證方式是交給了AuthenticationProvider來負責。

public interface AuthenticationProvider {
    /**
    *使用與AuthenticationManager的authenticate方法相同的
    *協定執行身份驗證(例如按照什么規則拋出異常等)
    */
    Authentication authenticate(Authentication authentication)
            throws AuthenticationException;

    /**
    *如果支持指定的Authentication 對象,則返回true</code
    */
    boolean supports(Class<?> authentication);
}

下面展示了AuthenticationProvider的部分實現

AuthenticationProvider的部分實現

AuthenticationManager與AuthenticationProvider 是怎么被創建使用,以及如何自由的添加認證方式的問題,在下面的內容中將進一步分析


2,認證方式的全局配置

前面章節分析了WebSecurityConfiguration配置類里面相關的內容,現在回到@EnableWebSecurity 注解上,我們接著分析下@EnableGlobalAuthentication內部都做了些啥

@Retention(value = java.lang.annotation.RetentionPolicy.RUNTIME)
@Target(value = { java.lang.annotation.ElementType.TYPE })
@Documented
@Import({ WebSecurityConfiguration.class,
        SpringWebMvcImportSelector.class,
        OAuth2ImportSelector.class })
@EnableGlobalAuthentication
@Configuration
public @interface EnableWebSecurity {

    /**
     * Controls debugging support for Spring Security. Default is false.
     * @return if true, enables debug support with Spring Security
     */
    boolean debug() default false;
}
@EnableGlobalAuthentication
@Retention(value = java.lang.annotation.RetentionPolicy.RUNTIME)
@Target(value = { java.lang.annotation.ElementType.TYPE })
@Documented
@Import(AuthenticationConfiguration.class)
@Configuration
public @interface EnableGlobalAuthentication {
}

其重點就是導入了AuthenticationConfiguration配置對象

@Configuration(proxyBeanMethods = false)
@Import(ObjectPostProcessorConfiguration.class)
public class AuthenticationConfiguration {
  ... ...
}

AuthenticationConfiguration 中還導入了ObjectPostProcessorConfiguration配置,該配置比較簡單,就是實例化了一個bean,而該Bean在前面的章節中也在不斷的用到

@Configuration(proxyBeanMethods = false)
public class ObjectPostProcessorConfiguration {

    @Bean
    public ObjectPostProcessor<Object> objectPostProcessor(
            AutowireCapableBeanFactory beanFactory) {
        return new AutowireBeanFactoryObjectPostProcessor(beanFactory);
    }
}

3,AuthenticationConfiguration

下面,我們深入分析下AuthenticationConfiguration配置類的實現。

先簡單的說下AuthenticationManager構建的主體過程

  1. AuthenticationConfiguration中收集所有GlobalAuthenticationConfigurerAdapter類型的Bean并保存到globalAuthConfigurers中;
  2. AuthenticationConfiguration中創建AuthenticationManagerBuilder類型的實例對象DefaultPasswordEncoderAuthenticationManagerBuilder;
  3. 將globalAuthConfigurers里面的對象傳遞給AuthenticationManagerBuilder;
  4. 執行AuthenticationManagerBuilder的build()方法完成AuthenticationManager的構建;
  5. WebSecurityConfigurerAdapter中調用AuthenticationConfiguration的getAuthenticationManager()方法得到構建的AuthenticationManager類似對象;
@Configuration(proxyBeanMethods = false)
@Import(ObjectPostProcessorConfiguration.class)
public class AuthenticationConfiguration {

    private AtomicBoolean buildingAuthenticationManager = new AtomicBoolean();

    private ApplicationContext applicationContext;

    private AuthenticationManager authenticationManager;

    private boolean authenticationManagerInitialized;

    private List<GlobalAuthenticationConfigurerAdapter> globalAuthConfigurers = Collections
            .emptyList();

    private ObjectPostProcessor<Object> objectPostProcessor;


    @Autowired
    public void setApplicationContext(ApplicationContext applicationContext) {
        this.applicationContext = applicationContext;
    }

    @Autowired
    public void setObjectPostProcessor(ObjectPostProcessor<Object> objectPostProcessor) {
        this.objectPostProcessor = objectPostProcessor;
    }

    //收集GlobalAuthenticationConfigurerAdapter類型對象
    @Autowired(required = false)
    public void setGlobalAuthenticationConfigurers(
            List<GlobalAuthenticationConfigurerAdapter> configurers) {
        configurers.sort(AnnotationAwareOrderComparator.INSTANCE);
        this.globalAuthConfigurers = configurers;
    }

    @Bean
    public static GlobalAuthenticationConfigurerAdapter enableGlobalAuthenticationAutowiredConfigurer(
            ApplicationContext context) {
        //GlobalAuthenticationConfigurerAdapter類型對象
        return new EnableGlobalAuthenticationAutowiredConfigurer(context);
    }

    //目的是構建默認的DaoAuthenticationProvider
    @Bean
    public static InitializeUserDetailsBeanManagerConfigurer initializeUserDetailsBeanManagerConfigurer(ApplicationContext context) {
        //GlobalAuthenticationConfigurerAdapter類型對象
        return new InitializeUserDetailsBeanManagerConfigurer(context);
    }

    //目的是將ApplicationContext中存在的AuthenticationProvider類型直接添加進來
    @Bean
    public static InitializeAuthenticationProviderBeanManagerConfigurer initializeAuthenticationProviderBeanManagerConfigurer(ApplicationContext context) {
        //GlobalAuthenticationConfigurerAdapter類型對象
        return new InitializeAuthenticationProviderBeanManagerConfigurer(context);
    }

    //身份驗證管理器生成器【下面個方法會獲取使用】
    @Bean
    public AuthenticationManagerBuilder authenticationManagerBuilder(
            ObjectPostProcessor<Object> objectPostProcessor, ApplicationContext context) {

        //創建“惰性”密碼編碼器,所謂“惰性”是指在真正使用到某個方法時才去到ApplicationContext中
        //獲取PasswordEncoder類型的對象進行調用【對于不確定某對象何時加入到ApplicationContext是挺有用】
        //默認情況,如果ApplicationContext沒有得到PasswordEncoder類型的實例,
        //則使用默認的DelegatingPasswordEncoder
        LazyPasswordEncoder defaultPasswordEncoder = new LazyPasswordEncoder(context);

        //從ApplicationContext中獲取AuthenticationEventPublisher類型的bean
        AuthenticationEventPublisher authenticationEventPublisher = getBeanOrNull(context, AuthenticationEventPublisher.class);

        //創建默認的AuthenticationManagerBuilder實例
        DefaultPasswordEncoderAuthenticationManagerBuilder result = new DefaultPasswordEncoderAuthenticationManagerBuilder(objectPostProcessor, defaultPasswordEncoder);
        if (authenticationEventPublisher != null) {
            result.authenticationEventPublisher(authenticationEventPublisher);
        }
        return result;
    }

    //創建身份認證管理器實例
    //注意:該方法在WebSecurityConfigurerAdapter中被調用
    public AuthenticationManager getAuthenticationManager() throws Exception {
        if (this.authenticationManagerInitialized) {
            return this.authenticationManager;
        }
        //獲取身份驗證管理器生成器DefaultPasswordEncoderAuthenticationManagerBuilder 
        AuthenticationManagerBuilder authBuilder = this.applicationContext.getBean(AuthenticationManagerBuilder.class);
        if (this.buildingAuthenticationManager.getAndSet(true)) {  //第一次調用不會進入,后面都會進入
            return new AuthenticationManagerDelegator(authBuilder);
        }

        for (GlobalAuthenticationConfigurerAdapter config : globalAuthConfigurers) {
            authBuilder.apply(config);
        }

        //重點需要關注這個方法執行的過程【重點】
        authenticationManager = authBuilder.build();

        if (authenticationManager == null) {
            authenticationManager = getAuthenticationManagerBean();
        }

        this.authenticationManagerInitialized = true;
        return authenticationManager;
    }

    private AuthenticationManager getAuthenticationManagerBean() {
        return lazyBean(AuthenticationManager.class);
    }
}

3.1 ,DefaultPasswordEncoderAuthenticationManagerBuilder

由上門可知,其默認使用DefaultPasswordEncoderAuthenticationManagerBuilder作為認證管理的構建器,下面分析其build()方法的執行過程。


AuthenticationManagerBuilder

DefaultPasswordEncoderAuthenticationManagerBuilder在執行build()方法時,其父類AbstractConfiguredSecurityBuilder的doBuild()方法被執行,前面有說過,這個方法是個模板方法,如下所示:

public abstract class AbstractConfiguredSecurityBuilder<O, B extends SecurityBuilder<O>>
        extends AbstractSecurityBuilder<O> {
    private final Log logger = LogFactory.getLog(getClass());

    private final LinkedHashMap<Class<? extends SecurityConfigurer<O, B>>, List<SecurityConfigurer<O, B>>> configurers = new LinkedHashMap<>();
    private final List<SecurityConfigurer<O, B>> configurersAddedInInitializing = new ArrayList<>();

    private final Map<Class<?>, Object> sharedObjects = new HashMap<>();

    private final boolean allowConfigurersOfSameType;

    private BuildState buildState = BuildState.UNBUILT;

    private ObjectPostProcessor<Object> objectPostProcessor;

    //模板方法
    protected final O doBuild() throws Exception {
        synchronized (configurers) {
            buildState = BuildState.INITIALIZING;

            beforeInit();
            init();  //這個方法的執行是關鍵

            buildState = BuildState.CONFIGURING;

            beforeConfigure();
            configure();

            buildState = BuildState.BUILDING;

            O result = performBuild();  //執行子類的具體實現

            buildState = BuildState.BUILT;

            return result;
        }
    }

    protected abstract O performBuild() throws Exception;

    @SuppressWarnings("unchecked")
    private void init() throws Exception {

        //當前這里存儲的是GlobalAuthenticationConfigurerAdapter類型的實例,
        //默認的有:InitializeUserDetailsBeanManagerConfigurer、
        //InitializeAuthenticationProviderBeanManagerConfigurer、
        //EnableGlobalAuthenticationAutowiredConfigurer
        Collection<SecurityConfigurer<O, B>> configurers = getConfigurers();

        for (SecurityConfigurer<O, B> configurer : configurers) {
            configurer.init((B) this);
        }

        for (SecurityConfigurer<O, B> configurer : configurersAddedInInitializing) {
            configurer.init((B) this);
        }
    }

    @SuppressWarnings("unchecked")
    private void configure() throws Exception {
        //當前這里存儲的是GlobalAuthenticationConfigurerAdapter類型的實例,
        //默認的有:InitializeUserDetailsBeanManagerConfigurer、
        //InitializeAuthenticationProviderBeanManagerConfigurer、
        //EnableGlobalAuthenticationAutowiredConfigurer
        Collection<SecurityConfigurer<O, B>> configurers = getConfigurers();

        for (SecurityConfigurer<O, B> configurer : configurers) {
            configurer.configure((B) this);
        }
    }
}

接著我們分析下這三個默認的GlobalAuthenticationConfigurerAdapter類型的實例中init和configure方法都做了啥

3.2 InitializeUserDetailsBeanManagerConfigurer

該類的目的純粹是為了添加InitializeUserDetailsManagerConfigurer配置,通過在其configure方法階段創建DaoAuthenticationProvider對象,最終被添加到ProviderManager中

@Order(InitializeUserDetailsBeanManagerConfigurer.DEFAULT_ORDER)
class InitializeUserDetailsBeanManagerConfigurer
        extends GlobalAuthenticationConfigurerAdapter {

    static final int DEFAULT_ORDER = Ordered.LOWEST_PRECEDENCE - 5000;

    private final ApplicationContext context;

    /**
     * @param context
     */
    InitializeUserDetailsBeanManagerConfigurer(ApplicationContext context) {
        this.context = context;
    }

    //注意:這里并沒有重寫configure方法
    @Override
    public void init(AuthenticationManagerBuilder auth) throws Exception {
        //直接添加InitializeUserDetailsManagerConfigurer配置類
        auth.apply(new InitializeUserDetailsManagerConfigurer());
    }

    class InitializeUserDetailsManagerConfigurer
            extends GlobalAuthenticationConfigurerAdapter {
        //重寫configure方法
        @Override
        public void configure(AuthenticationManagerBuilder auth) throws Exception {
            if (auth.isConfigured()) {
                return;
            }
            //用于根據用戶名得到用戶信息
            //Springboot的自動化配置中會默認創建InMemoryUserDetailsManager
            UserDetailsService userDetailsService = getBeanOrNull(
                    UserDetailsService.class);
            if (userDetailsService == null) {
                return;
            }

            //用于對用戶的密碼進行加密以及密碼比對驗證
            PasswordEncoder passwordEncoder = getBeanOrNull(PasswordEncoder.class);
            //用戶更新用戶密碼
            UserDetailsPasswordService passwordManager = getBeanOrNull(UserDetailsPasswordService.class);

            DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
            provider.setUserDetailsService(userDetailsService);
            if (passwordEncoder != null) {
                provider.setPasswordEncoder(passwordEncoder);
            }
            if (passwordManager != null) {
                provider.setUserDetailsPasswordService(passwordManager);
            }
            //用于驗證provider中的userDetailsService是否為空【不許為空】
            provider.afterPropertiesSet();
            //直接添加回DefaultPasswordEncoderAuthenticationManagerBuilder
            auth.authenticationProvider(provider);
        }

        /**
         * @return a bean of the requested class if there's just a single registered component, null otherwise.
         */
        private <T> T getBeanOrNull(Class<T> type) {
            String[] userDetailsBeanNames = InitializeUserDetailsBeanManagerConfigurer.this.context
                    .getBeanNamesForType(type);
            if (userDetailsBeanNames.length != 1) {
                return null;
            }

            return InitializeUserDetailsBeanManagerConfigurer.this.context
                    .getBean(userDetailsBeanNames[0], type);
        }
    }
}

Springboot的自動化配置中會默認創建InMemoryUserDetailsManager,請參考Spring Security解析二:自動化裝配

我們也可以通過配置來指定,例如:

public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth
            .jdbcAuthentication()
            .dataSource(dataSource)
            .withDefaultSchema()
            .withUser("user").password("password").roles("USER").and()
            .withUser("admin").password("password").roles("USER", "ADMIN");
}

接著進一步研究下DaoAuthenticationProvider都做了些啥,它是怎么對身份進行認證的?

DaoAuthenticationProvider繼承關系

3.2.1 AbstractUserDetailsAuthenticationProvider

public abstract class AbstractUserDetailsAuthenticationProvider implements
        AuthenticationProvider, InitializingBean, MessageSourceAware {

    protected final Log logger = LogFactory.getLog(getClass());

    // ~ Instance fields
    // ================================================================================================

    protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();
    private UserCache userCache = new NullUserCache();
    private boolean forcePrincipalAsString = false;
    protected boolean hideUserNotFoundExceptions = true;
    private UserDetailsChecker preAuthenticationChecks = new DefaultPreAuthenticationChecks();
    private UserDetailsChecker postAuthenticationChecks = new DefaultPostAuthenticationChecks();
    private GrantedAuthoritiesMapper authoritiesMapper = new NullAuthoritiesMapper();

    protected abstract void additionalAuthenticationChecks(UserDetails userDetails,
            UsernamePasswordAuthenticationToken authentication)
            throws AuthenticationException;

    protected abstract UserDetails retrieveUser(String username,
            UsernamePasswordAuthenticationToken authentication)
            throws AuthenticationException;


    //說明該身份認證方式僅適合UsernamePasswordAuthenticationToken
    @Override
    public boolean supports(Class<?> authentication) {
        return (UsernamePasswordAuthenticationToken.class
                .isAssignableFrom(authentication));
    }

    @Override
    public Authentication authenticate(Authentication authentication)
            throws AuthenticationException {
        Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication,
                () -> messages.getMessage(
                        "AbstractUserDetailsAuthenticationProvider.onlySupports",
                        "Only UsernamePasswordAuthenticationToken is supported"));

        // 從認證主體中得到用戶名(或返回未提供-NONE_PROVIDED)
        String username = (authentication.getPrincipal() == null) ? "NONE_PROVIDED"
                : authentication.getName();

        boolean cacheWasUsed = true;
        //從緩存中取得用戶信息(默認使用的緩存是NullUserCache)
        UserDetails user = this.userCache.getUserFromCache(username);

        if (user == null) {
            cacheWasUsed = false;

            try {
                //檢索用戶【由子類實現】
                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 {
            //檢測UserDetails的狀態【是否被鎖、是否可用、是否過期】
            preAuthenticationChecks.check(user);
            //子類執行任何附加檢查,例如:驗證密碼是否匹配
            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;
            }
        }

        //檢測UserDetails的狀態【憑據是否未過期】
        postAuthenticationChecks.check(user);

        if (!cacheWasUsed) {
            //將通過檢查的用戶信息加入緩存 
            this.userCache.putUserInCache(user);
        }

        Object principalToReturn = user;

        if (forcePrincipalAsString) {  //值返回用戶名
            principalToReturn = user.getUsername();
        }
        //返回成功的響應
        return createSuccessAuthentication(principalToReturn, authentication, user);
    }

    //注意:子類有重寫
    protected Authentication createSuccessAuthentication(Object principal,
            Authentication authentication, UserDetails user) {
        // Ensure we return the original credentials the user supplied,
        // so subsequent attempts are successful even with encoded passwords.
        // Also ensure we return the original getDetails(), so that future
        // authentication events after cache expiry contain the details
        UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(
                principal, authentication.getCredentials(),
                authoritiesMapper.mapAuthorities(user.getAuthorities()));
        result.setDetails(authentication.getDetails());

        return result;
    }
}

可見上的操作主要是從某個地方得到用戶信息,然后檢查用戶的狀態,如果檢查失敗則拋出相應的異常,否則返回成功的認證信息。
上面的retrieveUser與additionalAuthenticationChecks是需要繼續研究的地方

3.2.2 DaoAuthenticationProvider

public class DaoAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {

    private PasswordEncoder passwordEncoder;

    private UserDetailsService userDetailsService;

    private UserDetailsPasswordService userDetailsPasswordService;

    public DaoAuthenticationProvider() {
        setPasswordEncoder(PasswordEncoderFactories.createDelegatingPasswordEncoder());
    }

    @SuppressWarnings("deprecation")
    @Override
    protected void additionalAuthenticationChecks(UserDetails userDetails,
            UsernamePasswordAuthenticationToken authentication)
            throws AuthenticationException {
        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.matches(presentedPassword, userDetails.getPassword())) {
            logger.debug("Authentication failed: password does not match stored value");

            throw new BadCredentialsException(messages.getMessage(
                    "AbstractUserDetailsAuthenticationProvider.badCredentials",
                    "Bad credentials"));
        }
    }

    @Override
    protected final UserDetails retrieveUser(String username,
            UsernamePasswordAuthenticationToken authentication)
            throws AuthenticationException {
        prepareTimingAttackProtection();
        try {
            UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);
            if (loadedUser == null) {
                throw new InternalAuthenticationServiceException(
                        "UserDetailsService returned null, which is an interface contract violation");
            }
            return loadedUser;
        }
        catch (UsernameNotFoundException ex) {
            mitigateAgainstTimingAttack(authentication);
            throw ex;
        }
        catch (InternalAuthenticationServiceException ex) {
            throw ex;
        }
        catch (Exception ex) {
            throw new InternalAuthenticationServiceException(ex.getMessage(), ex);
        }
    }

    @Override
    protected Authentication createSuccessAuthentication(Object principal,
            Authentication authentication, UserDetails user) {
        boolean upgradeEncoding = this.userDetailsPasswordService != null
                && this.passwordEncoder.upgradeEncoding(user.getPassword());
        if (upgradeEncoding) {
            String presentedPassword = authentication.getCredentials().toString();
            String newPassword = this.passwordEncoder.encode(presentedPassword);
            user = this.userDetailsPasswordService.updatePassword(user, newPassword);
        }
        return super.createSuccessAuthentication(principal, authentication, user);
    }
}

上面通過userDetailsService來得到用戶的信息,并通過passwordEncoder來驗證密碼是否正確,而這兩個對象是通過上面 3.2小結里的InitializeUserDetailsManagerConfigurer中從ApplicationContext獲得。


3.3 InitializeAuthenticationProviderBeanManagerConfigurer

該類的目的純粹是為了添加InitializeUserDetailsManagerConfigurer配置,通過在其configure方法階段從ApplicationContext中得到AuthenticationProvider類型的Bean,并加入到ProviderManager中

@Order(InitializeAuthenticationProviderBeanManagerConfigurer.DEFAULT_ORDER)
class InitializeAuthenticationProviderBeanManagerConfigurer
        extends GlobalAuthenticationConfigurerAdapter {

    static final int DEFAULT_ORDER = InitializeUserDetailsBeanManagerConfigurer.DEFAULT_ORDER
            - 100;

    private final ApplicationContext context;

    /**
     * @param context the ApplicationContext to look up beans.
     */
    InitializeAuthenticationProviderBeanManagerConfigurer(
            ApplicationContext context) {
        this.context = context;
    }

    @Override
    public void init(AuthenticationManagerBuilder auth) throws Exception {
        auth.apply(new InitializeUserDetailsManagerConfigurer());
    }

    class InitializeUserDetailsManagerConfigurer
            extends GlobalAuthenticationConfigurerAdapter {
        @Override
        public void configure(AuthenticationManagerBuilder auth) {
            if (auth.isConfigured()) {
                return;
            }
            //直接從ApplicationContext中得到AuthenticationProvider類型Bean
            //也就是說,我們可以自定義一個,直接加入到ApplicationContext就可生效了,方便擴展
            AuthenticationProvider authenticationProvider = getBeanOrNull(
                    AuthenticationProvider.class);
            if (authenticationProvider == null) {
                return;
            }
            //這里直接添加到DefaultPasswordEncoderAuthenticationManagerBuilder中
            auth.authenticationProvider(authenticationProvider);
        }

        /**
         * @return
         */
        private <T> T getBeanOrNull(Class<T> type) {
            String[] userDetailsBeanNames = InitializeAuthenticationProviderBeanManagerConfigurer.this.context
                    .getBeanNamesForType(type);
            if (userDetailsBeanNames.length != 1) {
                return null;
            }

            return InitializeAuthenticationProviderBeanManagerConfigurer.this.context
                    .getBean(userDetailsBeanNames[0], type);
        }
    }
}

小結:

  1. Spring Security默認給我們創建了一個支持UsernamePasswordAuthenticationToken認證的AuthenticationProvider,里面用到了從ApplicationContext中取到的UserDetailsService、PasswordEncoder和UserDetailsPasswordService的實例對象;

  2. 我們可以自定義AuthenticationProvider實例來添加其它類型的驗證工作,同時,只需要將實例對象添加到ApplicationContext容器中即可生效。


經過上來的步驟后,在DefaultPasswordEncoderAuthenticationManagerBuilder的authenticationProviders屬性中添加了一個或多個AuthenticationProvider,接下來的工作便是執行DefaultPasswordEncoderAuthenticationManagerBuilder的performBuild()方法完成AuthenticationManager的創建工作。當然,其實該方法是在父類AuthenticationManagerBuilder中的。

public class AuthenticationManagerBuilder
        extends
        AbstractConfiguredSecurityBuilder<AuthenticationManager, AuthenticationManagerBuilder>
        implements ProviderManagerBuilder<AuthenticationManagerBuilder> {
    private final Log logger = LogFactory.getLog(getClass());

    private AuthenticationManager parentAuthenticationManager;
    private List<AuthenticationProvider> authenticationProviders = new ArrayList<>();
    private UserDetailsService defaultUserDetailsService;
    private Boolean eraseCredentials;
    private AuthenticationEventPublisher eventPublisher;

    @Override
    protected ProviderManager performBuild() throws Exception {
        if (!isConfigured()) {
            logger.debug("No authenticationProviders and no parentAuthenticationManager defined. Returning null.");
            return null;
        }
        ProviderManager providerManager = new ProviderManager(authenticationProviders,
                parentAuthenticationManager);
        if (eraseCredentials != null) {
            providerManager.setEraseCredentialsAfterAuthentication(eraseCredentials);
        }
        if (eventPublisher != null) {
            providerManager.setAuthenticationEventPublisher(eventPublisher);
        }
        providerManager = postProcess(providerManager);
        return providerManager;
    }
}

返回的其實是ProviderManager,而ProviderManager可以看成是AuthenticationManager的代理對象,里面保存了多個AuthenticationManager的實現。

public class ProviderManager implements AuthenticationManager, MessageSourceAware,
        InitializingBean {
    // ~ Static fields/initializers
    // =====================================================================================

    private static final Log logger = LogFactory.getLog(ProviderManager.class);

    // ~ Instance fields
    // ================================================================================================

    private AuthenticationEventPublisher eventPublisher = new NullEventPublisher();
    private List<AuthenticationProvider> providers = Collections.emptyList();
    protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();

    //可以是ProviderManager,目的是當無法進行身份認證時,再使用這個AuthenticationManager進行認證
    private AuthenticationManager parent;
    private boolean eraseCredentialsAfterAuthentication = true;

    public ProviderManager(List<AuthenticationProvider> providers) {
        this(providers, null);
    }

    public ProviderManager(List<AuthenticationProvider> providers,
            AuthenticationManager parent) {
        Assert.notNull(providers, "providers list cannot be null");
        this.providers = providers;
        this.parent = parent;
        checkState();
    }

    public void afterPropertiesSet() {
        checkState();
    }

    private void checkState() {
        if (parent == null && providers.isEmpty()) {
            throw new IllegalArgumentException(
                    "A parent AuthenticationManager or a list "
                            + "of AuthenticationProviders is required");
        }
    }

    @Override
    public Authentication authenticate(Authentication authentication)
            throws AuthenticationException {
        Class<? extends Authentication> toTest = authentication.getClass();
        AuthenticationException lastException = null;
        AuthenticationException parentException = null;
        Authentication result = null;
        Authentication parentResult = null;
        boolean debug = logger.isDebugEnabled();

        //查找匹配的 AuthenticationProvider 來執行驗證
        for (AuthenticationProvider provider : getProviders()) {
            if (!provider.supports(toTest)) {
                continue;
            }
            try {
                result = provider.authenticate(authentication);

                if (result != null) {
                    copyDetails(authentication, result);
                    break;
                }
            }
            catch (AccountStatusException | InternalAuthenticationServiceException e) {
                prepareException(e, authentication);
                // SEC-546: Avoid polling additional providers if auth failure is due to
                // invalid account status
                throw e;
            } catch (AuthenticationException e) {
                lastException = e;
            }
        }

        if (result == null && parent != null) {
            // Allow the parent to try.
            try {
                result = parentResult = 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 = parentException = e;
            }
        }

        if (result != null) {
            if (eraseCredentialsAfterAuthentication
                    && (result instanceof CredentialsContainer)) {
                // Authentication is complete. Remove credentials and other secret data
                // from authentication
                ((CredentialsContainer) result).eraseCredentials();
            }

            // If the parent AuthenticationManager was attempted and successful than it will publish an AuthenticationSuccessEvent
            // This check prevents a duplicate AuthenticationSuccessEvent if the parent AuthenticationManager already published it
            if (parentResult == null) {
                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}"));
        }

        // If the parent AuthenticationManager was attempted and failed than it will publish an AbstractAuthenticationFailureEvent
        // This check prevents a duplicate AbstractAuthenticationFailureEvent if the parent AuthenticationManager already published it
        if (parentException == null) {
            prepareException(lastException, authentication);
        }

        throw lastException;
    }

}

Spring Security默認情況下為我們創建了一個基于用戶名和密碼進行驗證的AuthenticationManager實例,同時收集ApplicationContext中的AuthenticationProvider類型的Bean 一起添加到ProviderManager(AuthenticationManager的子類)中供需要的地方進行使用。

構造器
AuthenticationManager
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,622評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,716評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,746評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,991評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,706評論 6 413
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,036評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,029評論 3 450
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,203評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,725評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,451評論 3 361
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,677評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,161評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,857評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,266評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,606評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,407評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,643評論 2 380

推薦閱讀更多精彩內容