上一篇:https://lawsssscat.blog.csdn.net/article/details/105299270
下一篇:https://blog.csdn.net/LawssssCat/article/details/105321389

前面的代码下载:https://github.com/LawssssCat/v-security/tree/v2.2

前面说了拦截的源码,这里说认证的源码。

为什么要看认证源码,因为后面我们要自己写认证 😃

认证流程源码分析,包括三点:

  • 认证【处理流程】说明
  • 认证结果如何在多个【请求间共享】
  • 获取认证【用户信息】

认证流程说明

1 登录请求认证拦截

添加 UsernamePasswordAuthenticationFilter 断点

登录:http://localhost:8080/login2.html


用前端传来的 username 和 password 构建了 token 对象

用过其他安全框架的应该都知道,<mark>token 就是用户登录的凭证</mark>
而这里的 token 就是 authentication 接口的实现(即如前面所说,封装的认证的信息)

进入到 UsernamePasswordAuthenticationToken 构造内部

(下图)分别设置了 用户名,密码,和权限

  • super(null) 调用父类的构造,设置权限为null(因为还没有认证,所以为null)
  • principal 当事人(即用户名)
  • credentials 凭证(即密码)
  • setAuthenticated 当前存入的信息是否经过身份认证。false 说明还没经过身份认证

进入父类构造,可以看出,父类的构造参数为权限

回到 UsernamePasswordAuthenticationFilter

最后返回了 AuthenticationManager 的方法的处理结果。

2 认证管理器

这个管理器,本身不包含验证的逻辑,作用是用来管理 AuthenticationProvider

进入 authenticate 方法

会进入到 ProviderManager

ProviderManager 实现了 AuthenticationManager 接口

会进入到 ProviderManager 的 authenticate 方法

(上图)因为可能有很多不同的登录方式(用户密码登录、toekn登录、第三方授权登录等),每个登录方式的逻辑会不一样,自然的也会有很多provider。

(上图的)循环是会挨个询问 provider 是否支持当前的登录方式(如当前的用户名+密码方式)。

询问的方式,是调用 support 方法

3 认证提供者

如果遍历到了,支持当前 Authentication 的 provider , 就会来到下面代码

4 用户详情服务

我们进入 provider.authenticate 方法,看到 retrieveUser 方法
(<mark>这个步骤 底层会调用 UserDetailsService</mark>,然后返回UserDetails)

5 三次认证

回到 Provider 的 authenticate 方法

当获得 用户 UserDetails 封装信息后,会到 preAuthenticationChecks.check(user); 方法
这个方法做的是预检查(检查账号是否可用)

进入 preAuthenticationChecks.check(user); 方法

回到 Provider 的 authenticate 方法
到了执行 additionalAuthenticationChecks 步骤
附加的 检查(由实现的 Provider 决定如何进行附加校验,检查密码?检查token?等等等)

进入 additionalAuthenticationChecks
在 DaoAuthenticationProvider 里面,进行的是密码校验

6 返回已认证信息

回到 Provider 的 authenticate 方法

这时,返回的 successAuthentication 是通过下面构造完成的
注意,这时候 super参数不是null,最后 super.setAuthenticated(true) 参数时 true

认证流程完

done ~

认证结果如何在多个请求间共享

如何共享信息?想都不用想就是存session,但,但是以什么形式存储?是什么时候放入session的?又是什么时候从session中取出?

# 以 SecurityContext 形式存储

这里的上下文其实就是 Authentication 认证信息,只不过进行了一步的封***r>

封装流程涉及到下面两个类:SecurityContext、SecurityContextHolder

我们先看登录成功后的行为

首先,UsernamePasswordAuthenticationFilter 的登录(认证)是在 AbstractAuthenticationProcessingFilter 中进行的

进入successfulAuthentication 方法

登录成功后的行为我们前面定义过
就是 AuthenticationSuccessHandler 的实现类

# 在 session 中的存和取

存取是在 SecurityContextPersistenceFilter 里面进行

<mark>SecurityContextPersistenceFilter 是在整个过滤链的最前面</mark>

因为是在最前面,所以可以做到:

  • <mark>当请求进来时候,检查session是否有认证信息,如果有,就放入线程 context 里面。</mark>
  • <mark>当响应走出时候,检查线程 context 是否有认证信息,如果有,就放入session中。</mark>

<mark>这样就实现了多个请求间共享认证信息!</mark>

获取认证用户信息

  • SecurityContextHolder: 一个 ThreadLocal 作为当前线程 SecurityContext 的 key
  • SecurityContext: 一个当前线程的Spring Security 上下文 可以获得当前用户信息
  • Authentication: 认证信息

因此,获取用户认证信息只需要在 (任何 SecurityContextPersistenceFilter 之后调用用的类,如Controller) Controller调用方法:

SecurityContextHolder.getContext().getAuthentication() 即可。

如:我们在 UserController 中添加方法

    @GetMapping("/me")
    public Object getCurrentUser() {
        return SecurityContextHolder.getContext().getAuthentication();
    }

也可以直接在参数中接收 Authentication

public Object getCurrentUser(Authentication authentication) {
	...
}

或者 如果只想用UserDetails , 可以使用使用 @AuthenticationPrincipal

重启服务
登录http://localhost:8080/login2.html

访问:http://localhost:8080/user/me

获得信息

  • authorities:授权信息
  • sessionID 和 远程IP
  • principal: 当前用户信息(注意,这里密码是null)

为什么密码是 null 呢?
那是在 sesstionStrategy 中决定的。
当认证成功后,AbstractAuthenticationProcessingFilter 会马上把认证结果放入 session

done