Spring Security用户认证成功失败源码分析

通常用户登录成功或者失败之后要做一些处理,比如日志记录、数据初始化等等;Spring中提供了事件及监听器,而Spring Security很好的运用了这一特点,框架中用了很多的事件来处理,要想很好的控制这些,先熟悉下源码,要知其所以然。

ProviderManager类分析

已经对该类进行过分析,侧重点不太一样

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

    for (AuthenticationProvider provider : getProviders()) {
        if (!provider.supports(toTest)) {
            continue;
        }

        if (debug) {
            logger.debug("Authentication attempt using "
                    + provider.getClass().getName());
        }

        try {
            //调用用户自定义的认证处理方法
            result = provider.authenticate(authentication);

            if (result != null) {
                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 {
            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;
    }
}

private void prepareException(AuthenticationException ex, Authentication auth) {
    //发布失败事件
    eventPublisher.publishAuthenticationFailure(ex, auth);
}

发布事件对象eventPublisher是一个AuthenticationEventPublisher类型,它是一个接口,里面有如下两个方法:

public interface AuthenticationEventPublisher {
    //发布认证成功事件
    void publishAuthenticationSuccess(Authentication authentication);
    //发布认证失败事件
    void publishAuthenticationFailure(AuthenticationException exception,
            Authentication authentication);
}

我们在ProviderManager类中看到eventPublisher的初始化值是一个NullEventPublisher对象,但是NullEventPublisher类中没有做任何的处理,所以推测肯定有其它地方对eventPublisher进行初始化,ProviderManager类中有一个setAuthenticationEventPublisher方法:

public void setAuthenticationEventPublisher(
        AuthenticationEventPublisher eventPublisher) {
    Assert.notNull(eventPublisher, "AuthenticationEventPublisher cannot be null");
    this.eventPublisher = eventPublisher;
}

上面的方法就是初始化事件发布对象的入口,这个方法是由AuthenticationManagerBuilder类来进行初始化的

AuthenticationManagerBuilder类分析

@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类中的发布事件进行初始化的;继续查看代码发现AuthenticationManagerBuilder类中存在eventPublisher对象及初始化方法,即:

private AuthenticationEventPublisher eventPublisher;

public AuthenticationManagerBuilder authenticationEventPublisher(
        AuthenticationEventPublisher eventPublisher) {
    Assert.notNull(eventPublisher, "AuthenticationEventPublisher cannot be null");
    this.eventPublisher = eventPublisher;
    return this;
}

那么是由哪个类调用AuthenticationManagerBuilderauthenticationEventPublisher初始化方法,接下来看下WebSecurityConfigurerAdapter

WebSecurityConfigurerAdapter类分析

public void init(final WebSecurity web) throws Exception {
    final HttpSecurity http = getHttp();
    web.addSecurityFilterChainBuilder(http).postBuildAction(new Runnable() {
        public void run() {
            FilterSecurityInterceptor securityInterceptor = http
                    .getSharedObject(FilterSecurityInterceptor.class);
            web.securityInterceptor(securityInterceptor);
        }
    });
}

上面的init方法调用了getHttp方法

protected final HttpSecurity getHttp() throws Exception {
    if (http != null) {
        return http;
    }
    //创建DefaultAuthenticationEventPublisher实例对象
    DefaultAuthenticationEventPublisher eventPublisher = objectPostProcessor
            .postProcess(new DefaultAuthenticationEventPublisher());
    //设置AuthenticationManagerBuilder对象的事件发布对象
    localConfigureAuthenticationBldr.authenticationEventPublisher(eventPublisher);

    AuthenticationManager authenticationManager = authenticationManager();
    authenticationBuilder.parentAuthenticationManager(authenticationManager);
    authenticationBuilder.authenticationEventPublisher(eventPublisher);
    Map<Class<? extends Object>, Object> sharedObjects = createSharedObjects();

    ...
}

上面的源码已经注解,事件发布的真实对象就是DefaultAuthenticationEventPublisher,它是AuthenticationEventPublisher接口的实现,接下来看下源码分析

AuthenticationEventPublisher源码分析

public class DefaultAuthenticationEventPublisher implements AuthenticationEventPublisher,
        ApplicationEventPublisherAware {
    ...
    //添加事件子类到集合中
    public DefaultAuthenticationEventPublisher(
            ApplicationEventPublisher applicationEventPublisher) {
        this.applicationEventPublisher = applicationEventPublisher;

        addMapping(BadCredentialsException.class.getName(),
                AuthenticationFailureBadCredentialsEvent.class);
        addMapping(UsernameNotFoundException.class.getName(),
                AuthenticationFailureBadCredentialsEvent.class);
        addMapping(AccountExpiredException.class.getName(),
                AuthenticationFailureExpiredEvent.class);
        addMapping(ProviderNotFoundException.class.getName(),
                AuthenticationFailureProviderNotFoundEvent.class);
        addMapping(DisabledException.class.getName(),
                AuthenticationFailureDisabledEvent.class);
        addMapping(LockedException.class.getName(),
                AuthenticationFailureLockedEvent.class);
        addMapping(AuthenticationServiceException.class.getName(),
                AuthenticationFailureServiceExceptionEvent.class);
        addMapping(CredentialsExpiredException.class.getName(),
                AuthenticationFailureCredentialsExpiredEvent.class);
        addMapping(
                "org.springframework.security.authentication.cas.ProxyUntrustedException",
                AuthenticationFailureProxyUntrustedEvent.class);
    }
    //发布认证成功方法
    public void publishAuthenticationSuccess(Authentication authentication) {
        if (applicationEventPublisher != null) {
            //真实发布认证成功事件
            applicationEventPublisher.publishEvent(new AuthenticationSuccessEvent(
                    authentication));
        }
    }
    //发布认证失败方法
    public void publishAuthenticationFailure(AuthenticationException exception,
            Authentication authentication) {
        Constructor<? extends AbstractAuthenticationEvent> constructor = exceptionMappings
                .get(exception.getClass().getName());
        AbstractAuthenticationEvent event = null;

        if (constructor != null) {
            try {
                event = constructor.newInstance(authentication, exception);
            }
            catch (IllegalAccessException ignored) {
            }
            catch (InstantiationException ignored) {
            }
            catch (InvocationTargetException ignored) {
            }
        }

        if (event != null) {
            if (applicationEventPublisher != null) {
                //真实发布认证失败事件
                applicationEventPublisher.publishEvent(event);
            }
        }
        else {
            if (logger.isDebugEnabled()) {
                logger.debug("No event was found for the exception "
                        + exception.getClass().getName());
            }
        }
    }

    ...
}

至此Spring Security中的事件发布采用倒叙的方式已经将源码分析完毕

本文转载参考 原文 并加以调试

版权声明:
作者:Joe.Ye
链接:https://www.appblog.cn/index.php/2023/03/20/source-code-analysis-of-successful-and-failed-spring-security-user-authentication/
来源:APP全栈技术分享
文章版权归作者所有,未经允许请勿转载。

THE END
分享
二维码
打赏
海报
Spring Security用户认证成功失败源码分析
通常用户登录成功或者失败之后要做一些处理,比如日志记录、数据初始化等等;Spring中提供了事件及监听器,而Spring Security很好的运用了这一特点,框架中用……
<<上一篇
下一篇>>
文章目录
关闭
目 录