美文网首页Java
Spring Security 源码之 UsernamePass

Spring Security 源码之 UsernamePass

作者: AlienPaul | 来源:发表于2020-11-12 16:48 被阅读0次

UsernamePasswordAuthenticationFilter

用于表单登陆认证,从表单获取username和password,然后执行认证逻辑。

认证的主要逻辑位于attemptAuthentication方法。如下所示:

public Authentication attemptAuthentication(HttpServletRequest request,
        HttpServletResponse response) throws AuthenticationException {
    // 如果只支持post请求,验证request是否是POST方式
    if (postOnly && !request.getMethod().equals("POST")) {
        throw new AuthenticationServiceException(
                "Authentication method not supported: " + request.getMethod());
    }

    // 从Request parameter的用户名和密码字段(字段名可自定义)获取获取username和password
    String username = obtainUsername(request);
    String password = obtainPassword(request);

    if (username == null) {
        username = "";
    }

    if (password == null) {
        password = "";
    }

    username = username.trim();

    // 构造authentication token
    UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
            username, password);

    // Allow subclasses to set the "details" property
    // 设置认证详细信息,例如remote IP地址和session id等
    setDetails(request, authRequest);

    // 使用认证管理器,调用认证流程,后面专门介绍AuthenticationManager
    return this.getAuthenticationManager().authenticate(authRequest);
}

要知道以上逻辑是如何同filter结合在一起的,我们需要分析下它的父类AbstractAuthenticationProcessingFilter

AbstractAuthenticationProcessingFilter

AbstractAuthenticationProcessingFilter是基于HTTP认证处理filter的抽象父类。

我们分析它的doFilter方法:

private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
        throws IOException, ServletException {
    // 使用RequestMatcher判断请求是否需要经过认证
    // 如果不需要认证直接跳过
    if (!requiresAuthentication(request, response)) {
        chain.doFilter(request, response);
        return;
    }
    try {
        // 尝试进行认证操作
        // 认证的逻辑在attemptAuthentication方法,是一个抽象方法,需要在子类中实现
        Authentication authenticationResult = attemptAuthentication(request, response);
        // 如果认证结果为null,说明认证未完成,直接返回
        if (authenticationResult == null) {
            // return immediately as subclass has indicated that it hasn't completed
            return;
        }
        // SessionAuthenticationStrategy主要负责处理认证成功的时候session需要执行的逻辑
        // 比如修改session id,用来防御session fixation攻击
        this.sessionStrategy.onAuthentication(authenticationResult, request, response);
        // Authentication success
        // 如果认证成功后需要执行后面的filter
        if (this.continueChainBeforeSuccessfulAuthentication) {
            chain.doFilter(request, response);
        }
        // 执行认证成功后的相关逻辑,包含:
        // 设置SecurityContext
        // 通知rememberMeService登陆成功
        // 广播登陆成功事件
        // 通知successHandler
        successfulAuthentication(request, response, chain, authenticationResult);
    }
    // 如果遇到了错误,调用认证失败逻辑
    catch (InternalAuthenticationServiceException failed) {
        this.logger.error("An internal error occurred while trying to authenticate the user.", failed);
        unsuccessfulAuthentication(request, response, failed);
    }
    catch (AuthenticationException ex) {
        // Authentication failed
        unsuccessfulAuthentication(request, response, ex);
    }
}

unsuccessfulAuthentication包含了认证失败的逻辑,代码如下:

protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response,
        AuthenticationException failed) throws IOException, ServletException {
    // 清空SecurityContextHolder中的认证用户信息
    SecurityContextHolder.clearContext();
    this.logger.trace("Failed to process authentication request", failed);
    this.logger.trace("Cleared SecurityContextHolder");
    this.logger.trace("Handling authentication failure");
    // 通知rememberMeServices登陆失败
    this.rememberMeServices.loginFail(request, response);
    // 通知failureHandler
    this.failureHandler.onAuthenticationFailure(request, response, failed);
}

SessionAuthenticationStrategy

主要负责处理认证成功的时候session需要执行的逻辑。

包含如下子类:

  • SessionFixationProtectionStrategy:创建一个新的session,将老session的属性迁移到新的session中
  • ChangeSessionIdAuthenticationStrategy:仍使用原来的session,仅修改下session id
  • ConcurrentSessionControlAuthenticationStrategy:限制用户同时登陆的次数
  • CompositeSessionAuthenticationStrategy:组合多个SessionAuthenticationStrategy
  • CsrfAuthenticationStrategy:登陆成功之后,更换原来的csrf token
  • NullAuthenticatedSessionStrategy:空实现,什么都不操作
  • RegisterSessionAuthenticationStrategy:注册新session信息到SessionRegistry

这些子类涉及的内容较多,详细分析请查阅Spring Security 源码之 SessionAuthenticationStrategy

AuthenticationSuccessHandler和AuthenticationFailureHandler

处理认证成功或失败之后的逻辑。

基本上每一种SuccessHandler都有对应的FailureHandler

下面主要分析下SuccessHandler,它包含如下子类:

  • ForwardAuthenticationSuccessHandler:重定向到特定的链接
  • SavedRequestAwareAuthenticationSuccessHandler:用于从requestCache中获取redirectURL信息并重定向到指定URL,如果requestCache没有,则使用默认targetUrl或者从request属性中获取,用于OAuth2认证环境
  • SimpleUrlAuthenticationSuccessHandler:用于从request中获取redirectURL信息并重定向到指定URL

UsernamePasswordAuthenticationToken

UsernamePasswordAuthenticationToken是用户认证信息的一个封装。保存了用户名,密码,拥有的权限和其他详细信息。

其中有一个布尔字段叫做authenticated。为true表示已经认证通过。

AuthenticationManager

用于执行认证操作这些类的父类。它有一个最重要的实现:ProviderManagerProviderManager包含了一个providers列表,为List<AuthenticationProvider>类型。这些AuthenticationProvider是多种认证方式的抽象,比如使用UserDetailsService读取用户信息认证,RemeberMe方式认证等。简单来说ProviderManager的认证过程是去遍历这些AuthenticationProvider,挨个尝试认证,如果有一个认证成功则认证通过。

我们查看下它的代码:

@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
    // 获取authentication的类型
    Class<? extends Authentication> toTest = authentication.getClass();
    AuthenticationException lastException = null;
    AuthenticationException parentException = null;
    Authentication result = null;
    Authentication parentResult = null;
    int currentPosition = 0;
    int size = this.providers.size();
    // 挨个遍历所有的provider
    for (AuthenticationProvider provider : getProviders()) {
        // 如果provider不支持这种类型的authentication,跳过
        if (!provider.supports(toTest)) {
            continue;
        }
        if (logger.isTraceEnabled()) {
            logger.trace(LogMessage.format("Authenticating request with %s (%d/%d)",
                    provider.getClass().getSimpleName(), ++currentPosition, size));
        }
        try {
            // 执行认证过程
            result = provider.authenticate(authentication);
            // 如果认证通过
            if (result != null) {
                // 将其他认证详细信息写入到result中
                copyDetails(authentication, result);
                // 退出for循环
                break;
            }
        }
        catch (AccountStatusException | InternalAuthenticationServiceException ex) {
            prepareException(ex, authentication);
            // SEC-546: Avoid polling additional providers if auth failure is due to
            // invalid account status
            throw ex;
        }
        catch (AuthenticationException ex) {
            lastException = ex;
        }
    }
    // 如果尝试完所有的认证方式都未认证成功
    if (result == null && this.parent != null) {
        // Allow the parent to try.
        try {
            // 使用parent AuthenticationManager进行认证
            parentResult = this.parent.authenticate(authentication);
            result = parentResult;
        }
        catch (ProviderNotFoundException ex) {
            // 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 ex) {
            parentException = ex;
            lastException = ex;
        }
    }
    // 如果认证成功
    if (result != null) {
        // 认证成功之后如果需要擦除密码凭证信息,在此处擦除
        if (this.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 then it
        // will publish an AuthenticationSuccessEvent
        // This check prevents a duplicate AuthenticationSuccessEvent if the parent
        // AuthenticationManager already published it
        // 发布认证成功事件
        // 这个if检测是用于防止parent AuthenticationManager认证成功重复发布事件
        if (parentResult == null) {
            this.eventPublisher.publishAuthenticationSuccess(result);
        }
        // 返回认证结果
        return result;
    }

    // Parent was null, or didn't authenticate (or throw an exception).
    // 如果parent为null,认证未成功或者抛出异常
    // 修改lastException为未找到provider异常
    if (lastException == null) {
        lastException = new ProviderNotFoundException(this.messages.getMessage("ProviderManager.providerNotFound",
                new Object[] { toTest.getName() }, "No AuthenticationProvider found for {0}"));
    }
    // If the parent AuthenticationManager was attempted and failed then it will
    // publish an AbstractAuthenticationFailureEvent
    // This check prevents a duplicate AbstractAuthenticationFailureEvent if the
    // parent AuthenticationManager already published it
    // 如果parent尝试认证失败,发布认证失败事件
    if (parentException == null) {
        prepareException(lastException, authentication);
    }
    throw lastException;
}

AuthenticationProvider

顾名思义AuthenticationProvider即认证方式提供者。Spring支持多种认证方式,AuthenticationProvider是这些认证方式统一实现的接口。
常用的AuthenticationProvider子类如下:

  • DaoAuthenticationProvider:从UserDetailsService中获取用户信息进行认证操作。
  • RememberMeAuthenticationProvider:提供RemeberMe方式认证。
  • JwtAuthenticationProvider:JWT方式认证。
  • AnonymousAuthenticationProvider:匿名方式认证

此部分涉及的内容较多,详细分析请查阅Spring Security 源码之 AuthenticationProvider

本文为原创内容,欢迎大家讨论、批评指正与转载。转载时请注明出处。

相关文章

网友评论

    本文标题:Spring Security 源码之 UsernamePass

    本文链接:https://www.haomeiwen.com/subject/awepbktx.html