史上最简单的Spring Security教程(二十七):AuthenticationManager默认实现之ProviderManager详解

 

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 或者其子类,可以单纯的作为例子来看,不作其它意义。

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

我是银河架构师,十年饮冰,难凉热血,愿历尽千帆,归来仍是少年! 

如果文章对您有帮助,请举起您的小手,轻轻【三连】,这将是笔者持续创作的动力源泉。当然,如果文章有错误,或者您有任何的意见或建议,请留言。感谢您的阅读!

 

源码

 

github

https://github.com/liuminglei/SpringSecurityLearning/tree/master/27

gitee

https://gitee.com/xbd521/SpringSecurityLearning/tree/master/27

 

 

  • 4
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值