SpringSecurity 认证流程

简介: 通过了解SpringSecurity核心组件后,就可以进一步了解其认证的实现流程了。

@[TOC]

前言

通过[上文]了解SpringSecurity核心组件后,就可以进一步了解其认证的实现流程了。

认证入口(过滤器)

在SpringSecurity中处理认证逻辑是在UsernamePasswordAuthenticationFilter这个过滤器中实现的。UsernamePasswordAuthenticationFilter继承于AbstractAuthenticationProcessingFilter这个父类。

image.png

而在UsernamePasswordAuthenticationFilter没有实现doFilter方法,所以认证的逻辑需要先看AbstractAuthenticationProcessingFilter中的doFilter方法。

image.png

上面的核心代码是

Authentication authenticationResult = attemptAuthentication(request, response);

attemptAuthentication方法的作用是获取Authentication对象对应的其实就是认证过程,进入到UsernamePasswordAuthenticationFilter中来查看具体的实现。

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
            throws AuthenticationException {
        if (this.postOnly && !request.getMethod().equals("POST")) {
            throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
        }
        String username = obtainUsername(request);
        username = (username != null) ? username : "";
        username = username.trim();
        String password = obtainPassword(request);
        password = (password != null) ? password : "";
        UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
        // Allow subclasses to set the "details" property
        setDetails(request, authRequest);
        return this.getAuthenticationManager().authenticate(authRequest);
    }

上面代码的含义非常清晰

  1. 该方法只支持POST方式提交的请求
  2. 获取账号和密码
  3. 通过账号密码获取了UsernamePasswordAuthenticationToken对象
  4. 设置请求的详细信息
  5. 通过AuthenticationManager来完成认证操作

在上面的逻辑中出现了一个对象AuthenticationManager

认证管理器

AuthenticationManager接口中就定义了一个方法authenticate方法,处理认证的请求。

public interface AuthenticationManager {

    Authentication authenticate(Authentication authentication) throws AuthenticationException;

}

认证器说明

AuthenticationManager的默认实现是ProviderManager,而在ProviderManagerauthenticate方法中实现的操作是循环遍历成员变量List<AuthenticationProvider> providers。该providers中如果有一个AuthenticationProvidersupports函数返回true,那么就会调用该AuthenticationProviderauthenticate函数认证,如果认证成功则整个认证过程结束。如果不成功,则继续使用下一个合适的AuthenticationProvider进行认证,只要有一个认证成功则为认证成功。

image.png

在当前环境下默认的实现提供是

image.png

默认认证器的实现

进入到AbstractUserDetailsAuthenticationProvider中的认证方法

image.png

然后进入到retrieveUser方法中,具体的实现是DaoAuthenticationProvidergetUserDetailsService会获取到我们自定义的UserServiceImpl对象,也就是会走我们自定义的认证方法了。

    @Override
    protected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication)
            throws AuthenticationException {
        prepareTimingAttackProtection();
        try {
     
            UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);
            if (loadedUser == null) {
                throw new InternalAuthenticationServiceException(
                        "UserDetailsService returned null, which is an interface contract violation");
            }
            return loadedUser;
        }
        catch (UsernameNotFoundException ex) {
            mitigateAgainstTimingAttack(authentication);
            throw ex;
        }
        catch (InternalAuthenticationServiceException ex) {
            throw ex;
        }
        catch (Exception ex) {
            throw new InternalAuthenticationServiceException(ex.getMessage(), ex);
        }
    }

如果账号存在就会开始密码的验证,不过在密码验证前还是会完成一个检查

image.png

image.png

然后就是具体的密码验证

additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken) authentication);

具体的验证的逻辑

    protected void additionalAuthenticationChecks(UserDetails userDetails,
            UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
        // 密码为空
        if (authentication.getCredentials() == null) {
            this.logger.debug("Failed to authenticate since no credentials provided");
            throw new BadCredentialsException(this.messages
                    .getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
        }
        // 获取表单提交的密码
        String presentedPassword = authentication.getCredentials().toString();
        // 表单提交的密码和数据库查询的密码 比较是否相对
        if (!this.passwordEncoder.matches(presentedPassword, userDetails.getPassword())) {
            this.logger.debug("Failed to authenticate since password does not match stored value");
            throw new BadCredentialsException(this.messages
                    .getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
        }
    }

上面的逻辑会通过对应的密码编码器来处理,如果是非加密的情况会通过NoOpPasswordEncoder来处理

    public boolean matches(CharSequence rawPassword, String encodedPassword) {
        return rawPassword.toString().equals(encodedPassword);
    }

image.png

如果有加密处理,就选择对应的加密对象来处理,比如SpringSecurity 入门使用的BCryptPasswordEncoder来处理

image.png

总结

Spring Security认证流程:

  1. 请求首先会进入过滤器,它的作用在于验证系统设置受限资源的过滤器。
  2. 请求被过滤器接收后,会将其传递给AuthenticationProvider。AuthenticationProvider需要用户的信息以及UserDetailsService的认证实现方式。
  3. UserDetailsService是被UserDetail继承的,UserDetail封装了User用户信息。在这个阶段,用户信息被封装进AuthenticationProvider。
  4. 接着,封装了用户信息的AuthenticationProvider会被传递给AuthenticationManager中的ProviderManager。

ProviderManager会对AuthenticationProvider进行再次审核,最终返回过滤器。

通过Spring Security的认证实现,可以看到虽然代码简洁明了,但是其扩展性极强。Spring Security支持多种认证和授权机制,包括用户名密码认证、JWT令牌认证、OAuth2认证等等。同时,Spring Security还提供了多种默认配置,可以根据需要进行调整和扩展。

相关文章
|
安全 Java 数据库
SpringSecurity-4-认证流程源码解析
SpringSecurity-4-认证流程源码解析
80 0
|
存储 安全 前端开发
详解SpringSecurity认证(下)
详解SpringSecurity认证(下)
125 0
|
安全 Java 数据库连接
四.SpringSecurity基础-自定义登录流程
SpringSecurity基础-自定义登录流程
|
安全 API
04 Shrio身份认证流程
04 Shrio身份认证流程
52 0
04 Shrio身份认证流程
盘点认证框架 : SpringSecurity 基础篇
SpringSecurity 应该是最常见的认证框架了 , 处于Spring体系中使他能快速地上手 , 这一篇开始作为入门级开篇作 , 来浅浅地讲一下SpringSecurity 的整体结构.
|
安全 Cloud Native Java
Spring与OAuth2:实现第三方认证和授权的最佳实践
Spring与OAuth2:实现第三方认证和授权的最佳实践
204 0
|
安全 前端开发 Java
详解SpringSecurity认证(上)
详解SpringSecurity认证(上)
167 0
|
安全 API 数据库
五.SpringSecurity基础-授权流程
SpringSecurity基础-授权流程
|
安全 API 数据库
SpringSecurity基础-授权流程
授权一定是在认证通过之后,授权流程是通过FilterSecurityInterceptor拦截器来完成,FilterSecurityInterceptor通过调用SecurityMetadataSource来获取当前访问的资源所需要的权限,然后通过调用AccessDecisionManager投票决定当前用户是否有权限访问当前资源。授权流程如下
138 0
|
JSON 前端开发 数据格式
六.SpringSecurity基础-认证授权结果处理
SpringSecurity基础-认证授权结果处理