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阅读 194,524评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,869评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,813评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,210评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,085评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,117评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,533评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,219评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,487评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,582评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,362评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,218评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,589评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,899评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,176评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,503评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,707评论 2 335

推荐阅读更多精彩内容