上一篇:https://lawsssscat.blog.csdn.net/article/details/105299270
下一篇:https://blog.csdn.net/LawssssCat/article/details/105321389
前面说了拦截的源码,这里说认证的源码。
为什么要看认证源码,因为后面我们要自己写认证 😃
认证流程源码分析,包括三点:
- 认证【处理流程】说明
- 认证结果如何在多个【请求间共享】
- 获取认证【用户信息】
认证流程说明
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 的 keySecurityContext
: 一个当前线程的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