美文网首页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