Pig4Cloud之登陆验证(一)客户端认证处理

前端登陆

handleLogin() {
      this.$refs.loginForm.validate(valid => {
        if (valid) {
          this.$store
              .dispatch("LoginByUsername", this.loginForm)
              .then(() => {
                this.$router.push({path: this.tagWel.value});
              })
              .catch(() => {
                this.refreshCode();
              });
        }
      });
    }

看一下LoginByUsername,在/src/store/modules/user.js中

const scope = 'server'

export const loginByUsername = (username, password, code, randomStr) => {
  const grant_type = 'password'
  let dataObj = qs.stringify({'username': username, 'password': password})

  let basicAuth = 'Basic ' + window.btoa(website.formLoginClient)

  // 保存当前选中的 basic 认证信息
  setStore({
    name: 'basicAuth',
    content: basicAuth,
    type: 'session'
  })

  return request({
    url: '/auth/oauth2/token',
    headers: {
      isToken: false,
      Authorization: basicAuth
    },
    method: 'post',
    params: {randomStr, code, grant_type, scope},
    data: dataObj
  })
}

客户端认证

当访问 OAuth2 相关接口时(/oauth2/token/oauth2/introspect/oauth2/revoke),授权服务器需要进行客户端认证。
Spring Authorization Server 截至目前支持如下五种客户端认证方式:client_secret_basicclient_secret_postclient_secret_jwtprivate_key_jwtnone (针对公共客户端)

OAuth2ClientAuthenticationFilter

实现客户端认证的拦截器就是 OAuth2ClientAuthenticationFilter。 其核心代码如下:

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {

    if (!this.requestMatcher.matches(request)) {
        filterChain.doFilter(request, response);
        return;
    }

    try {
        Authentication authenticationRequest = this.authenticationConverter.convert(request);
        if (authenticationRequest instanceof AbstractAuthenticationToken) {
            ((AbstractAuthenticationToken) authenticationRequest).setDetails(
                    this.authenticationDetailsSource.buildDetails(request));
        }
        if (authenticationRequest != null) {
            Authentication authenticationResult = this.authenticationManager.authenticate(authenticationRequest);
            this.authenticationSuccessHandler.onAuthenticationSuccess(request, response, authenticationResult);
        }
        filterChain.doFilter(request, response);

    } catch (OAuth2AuthenticationException ex) {
        this.authenticationFailureHandler.onAuthenticationFailure(request, response, ex);
    }
}

其核心逻辑就是通过 authenticationConverter从 request 中解析出客户端认证信息,构建成 Authentication,再通过 authenticationManager 对 Authentication 进行认证。

DelegatingAuthenticationConverter

authenticationConverter 的类型实际上是 DelegatingAuthenticationConverter,它持有一个 AuthenticationConverter 列表(不同的认证请求,其参数不同,所以会有不同的AuthenticationConverter实现类)。
DelegatingAuthenticationConverter 在解析请求时会遍历 AuthenticationConverter 列表,当某个 AuthenticationConverter 解析成功时,立即返回,这也能确定此请求是什么认证方式,后续再执行对应的认证逻辑。

private AuthenticationConverter authenticationConverter;

public OAuth2ClientAuthenticationFilter(AuthenticationManager authenticationManager,
            RequestMatcher requestMatcher) {
        Assert.notNull(authenticationManager, "authenticationManager cannot be null");
        Assert.notNull(requestMatcher, "requestMatcher cannot be null");
        this.authenticationManager = authenticationManager;
        this.requestMatcher = requestMatcher;
        this.authenticationConverter = new DelegatingAuthenticationConverter(
                Arrays.asList(
                        new JwtClientAssertionAuthenticationConverter(),
                        new ClientSecretBasicAuthenticationConverter(),
                        new ClientSecretPostAuthenticationConverter(),
                        new PublicClientAuthenticationConverter()));
    }

ProviderManager

authenticationManager 的类型实际上是 ProviderManager,它持有一个 AuthenticationProvider 列表(不同的认证方式,其认证逻辑不同,所以会有不同的AuthenticationProvider实现类)。

AuthenticationManager 接口的默认实现为ProviderManager

image

authenticationManager调用authenticate

image

调用 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方法进行身份认证。
此处的provider为ClientSecretAuthenticationProvider

image

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

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

            ......

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

ClientSecretAuthenticationProvider中调用RegisteredClientRepository,通过clientId去数据库查询client

image

image

如果认证成功且返回的结果不为 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());
    }
}

如果发生 AccountStatusExceptionInternalAuthenticationServiceException 异常,则会通过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为 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。

CSDN
腾讯云
掘金
博客园

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容