Spring Security是怎么用你的账号密码认证的?

184 阅读5分钟

前言

Spring security的登录和注销我们大概都了解了,然后核心类我们也了解了,接下来呢还有基础的,quick start我们也做了。就缺少一次成体系的流程介绍, 本篇将会以角色扮演的形式带你深入源码了解spring security底层是怎么认证的。

认证

是什么?

小白: "认证认证到底什么是认证呢?是直接比较密码吗?"

小黑: "认证的核心就是比较用户输入的账户和数据库中已经注册过的账户的密码是否相等。根据相等的结果,才能给予用户后续一系列授权和访问操作。"

小白: "那spring security的认证是和有什么区别吗?"

小黑: 总体来看的话基本上没什么区别,说白了就是用户输入的账户和数据库中的账户进行比较就这么简单。如果细分下来的话就有下列几个步骤:

  • 第一从客户端请求中拿到用户输入的账户和密码,

  • 第二从数据库中拿到数据库中的账户和密码,

  • 第三就是做比较。

  • 最后保存认证成功的信息。

这是论证主要的步骤当然还有其他功能。比如说remember me功能之类。

小白: "那看起来还挺简单的,那spring security源码也是这么搞的吗?"

小黑: "万变不离其宗,接下来我们将以上面总结的这几个步骤进行分析,spring security源码中的这些步骤所处的源码到底是怎么实现的"

小黑: "不过在分析源码之前,需要罗列一下过滤器执行顺序的列表。"

Spring security中过滤器列表

image-20221113045141957

image-20221113045206974

小黑: "这些过滤器不需要记得很详细,只需要在关键时刻拿出来看看就行了。"

开始分析

image-20221229182727351

直接从上图的过滤器开始分析,可以省掉很多事情。

UsernamePasswordAuthenticationFilter看看这个类的类族:

image-20221229182817884

经常分析源码的人应该知道, 接口对于java程序员而言是类似于功能, 我们添加的每一个接口就是一个功能, 所以我们可以看看UsernamePasswordAuthenticationFilter类都有哪些功能

我们可以直接看到它其实就是一个过滤器,而且是servlet的过滤器。

紧接着我们知道, 如果使用类, 最好使用类的接口, 不然不好扩展, 因为使用接口的话, 我们可以new各种子类

那么就可以找到doFilter的函数在这里AbstractAuthenticationProcessingFilter

image-20221229210040450

  • 第一从客户端请求中拿到用户输入的账户和密码,

  • 第二从数据库中拿到数据库中的账户和密码,

  • 第三就是做比较。

  • 最后保存认证成功的信息。

小白: "简直和我们说的过程一致, 完美"

从前端拿到账户和密码

这个过程主要在这里UsernamePasswordAuthenticationFilter#attemptAuthentication

image-20221229210730331

看看, 下面都是通过 request 获取的 usernamepassword 的源码

image-20221229210821248

如果需要进行前后端分离, 那么这两个函数可以修改哦, 当然你也可以使用在attemptAuthentication中获取 username password

从数据库中拿账户密码和密码匹配比较

这里需要进入下面源码才能看到上面的账户密码

this.getAuthenticationManager().authenticate(authRequest);

进入上面的那个函数只能看到ProviderManager, 就能够看到前面一章节熟悉的

AuthenticationManagerAuthenticationManager.parent 之间的关系, 父子AuthenticationManager关系

紧接着才会讲到我们需要的函数

AbstractUserDetailsAuthenticationProvider#authenticate

image-20221229214113954

image-20221229220652411

保存认证成功信息

AbstractAuthenticationProcessingFilter#successfulAuthentication

image-20221229214812220

最后在附上绝杀流程图

SpringSecurity认证流程

总结

  1. UsernamePasswordAuthenticationFilter的作用

    1. 获取用户名和密码

    2. 配置匹配器

    3. private static final AntPathRequestMatcher DEFAULT_ANT_PATH_REQUEST_MATCHER = new AntPathRequestMatcher("/login", "POST");
      
    4. 前端表单的username和password也可以在这里改

    5. public static final String SPRING_SECURITY_FORM_USERNAME_KEY = "username";
      public static final String SPRING_SECURITY_FORM_PASSWORD_KEY = "password";
      private String usernameParameter = SPRING_SECURITY_FORM_USERNAME_KEY;
      private String passwordParameter = SPRING_SECURITY_FORM_PASSWORD_KEY;
      
  2. AbstractAuthenticationProcessingFilter的作用是

    1. requiresAuthentication: 进行匹配器调用, 把一些不满足的请求过滤掉
    2. attemptAuthentication: 从前端获取用户名和密码, 并且进行认证请求this.getAuthenticationManager().authenticate(authRequest);
    3. onAuthentication: 认证成功后进行一些关于session的操作, 比如保存session内的部分信息或者验证session中的一些参数等等
    4. successfulAuthentication: 做一些认证成功之后的操作, 比如保存认证成功信息到SecurityContextHolder上下文, 比如remember-me记住我功能等等
    5. InteractiveAuthenticationSuccessEvent事件, 使用Spring Event发送的事件, 意味着认证和登录成功, 并且会发送一个Authentication, 里面保存了认证信息
  3. 重写UsernamePasswordAuthenticationFilter我们可以做很多事情, 比如原文说的前后端分离, 比如图片验证码功能等

  4. AuthenticationManager真正执行认证的过程, 而UsernamePasswordAuthenticationFilter主要的任务是拿到 username password 并且调用AuthenticationManager进行认证, 通常这个类都是ProviderManager

  5. ProviderManager: 这个类你就认为是一个集合, 里面遍历循环private List<AuthenticationProvider> providers

    1. 它还有 parent字段
    2. 可以在里面进行敏感数据删除eraseCredentials
  6. AuthenticationProvider : 真正执行认证业务的类, 如果是表单认证方式, 他会调用DaoAuthenticationProvider这里我们可以看出Provider的运行模式是support判断是否支持, 然后再执行业务

  7. DaoAuthenticationProvider: 这个类的功能

    1. additionalAuthenticationChecks: 判断密码是否相等
    2. retrieveUser: 从数据库中读取账户UserDetails, 如果你需要自定义那么需要重写UserDetailsService这个接口, 内容主要是从数据库中根据用户名读取用户名和密码