Spring Security 框架中的另一个重要接口 AuthenticationManager, 被设计用于处理 Authentication 请求。

与 AuthenticationProvider 接口一致,AuthenticationManager 接口中有且只有一个方法,即authenticate(Authentication authentication) 方法。

Authentication authenticate(Authentication authentication)
      throws AuthenticationException;

该方法与 AuthenticationProvider 中的 authenticate 方法声明及功能完全一致,返回包含凭据的完整身份验证对象 authentication。但是,如果 AuthenticationProvider 不支持给定的 Authentication 的话,该方法可能会返回 null。在此情况下,下一个支持 authentication 的 AuthenticationProvider 将会被尝试。

AuthenticationManager 接口的默认实现为 ProviderManager,其逻辑也不复杂。

首先,便是调用 AuthenticationProvider 中的 supports(Class<?> authentication) 方法,判断是否支持当前的 Authentication 请求。

public Authentication authenticate(Authentication authentication)
      throws AuthenticationException {
    ......

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

只有支持当前 Authentication 请求的 AuthenticationProvider 才会继续后续逻辑处理。

然后,便是调用 AuthenticationProvider 中的 authenticate(Authentication authentication) 方法进行身份认证。

public Authentication authenticate(Authentication authentication)
      throws AuthenticationException {
    ......

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

      ......

      try {
        result = provider.authenticate(authentication);
        ......
      }

如果认证成功且返回的结果不为 null,则执行 authentication details 的拷贝逻辑

try {
    result = provider.authenticate(authentication);

    if (result != null) {
        copyDetails(authentication, result);
        break;
    }
}

......

private void copyDetails(Authentication source, Authentication dest) {
    if ((dest instanceof AbstractAuthenticationToken) && (dest.getDetails() == null)) {
        AbstractAuthenticationToken token = (AbstractAuthenticationToken) dest;

        token.setDetails(source.getDetails());
    }
}

如果发生 AccountStatusException 或 InternalAuthenticationServiceException 异常,则会通过Spring事件发布器AuthenticationEventPublisher 发布异常事件。

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

......

private void prepareException(AuthenticationException ex, Authentication auth) {
    eventPublisher.publishAuthenticationFailure(ex, auth);
}

如果异常为其它类型的 AuthenticationException,则将此异常设置为 lastException 并返回。

catch (AuthenticationException e) {
    lastException = e;
}

如果认证结果为 null,且存在父 AuthenticationManager,则调用父 AuthenticationManager 进行同样的身份认证操作,其处理逻辑基本同上。

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

如果认证结果不为 null,同时,此时的 eraseCredentialsAfterAuthentication 参数为 true,且此时认证后的 Authentication 实现了 CredentialsContainer 接口,那么即调用 CredentialsContainer 接口的凭据擦除方法,即eraseCredentials,擦除相关凭据信息。

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

其中,有一个防止重复发布 AuthenticationSuccessEvent 事件的处理,即 parentResult 为空。如果 parentResult 为 null,则代表父 AuthenticationManager 不存在或者没有身份认证成功,也即没有发布过 AuthenticationSuccessEvent 事件。此时,便由此处发布 AuthenticationSuccessEvent 事件。

最后,便是对于 lastException 的相关处理。

如果 lastException 为 null,则代表当前的 Authentication 并没有对应支持的 Provider。此时,便会抛出相应异常。

if (lastException == null) {
    lastException = new ProviderNotFoundException(messages.getMessage(
        "ProviderManager.providerNotFound",
        new Object[] { toTest.getName() },
        "No AuthenticationProvider found for {0}"));
}

接下来,如同防止重复发布 AuthenticationSuccessEvent 事件的处理一样,也有一个防止 AbstractAuthenticationFailureEvent 事件重复发布的逻辑处理。如果 parentException 为 null,则代表父AuthenticationManager 不存在、没有进行身份认证或者发布过 AbstractAuthenticationFailureEvent 事件,此时,便由此处发布 AbstractAuthenticationFailureEvent 事件。

if (parentException == null) {
    prepareException(lastException, authentication);
}

throw lastException;

最后,抛出 lastException

但是,抛出 lastException 之后呢?其实,是被另外一个 Filter 捕获并初始化到当前用户的 Request 中,感兴趣的朋友可以关注后续的文章,会有详细的解释。

如同之前介绍其它重要接口一样,我们了解了其详细的逻辑以后,不妨自己自定义一个 AuthenticationManager 的实现。我们先不实现复杂的逻辑,就针对 UsernamePasswordAuthenticationToken 相对应的 DaoAuthenticationProvider 来实现一个 AuthenticationManager

public Authentication authenticate(Authentication authentication)
            throws AuthenticationException {
        ......

        if (provider.supports(toTest)) {
      ......

            try {
                result = provider.authenticate(authentication);
            }
       ......
            catch (AuthenticationException e) {
                lastException = e;
            }

            ......

        } else {
            lastException = new ProviderNotFoundException(messages.getMessage(
                    "ProviderManager.providerNotFound",
                    new Object[] { toTest.getName() },
                    "No AuthenticationProvider found for {0}"));
        }

        ......

        throw lastException;
    }

此 AuthenticationManager 只接受 DaoAuthenticationProvider 或者其子类,可以单纯的作为例子来看,不作其它意义。

其它详细源码,请参考文末源码链接,可自行下载后阅读。