SpringSecurity-认证流程源码级详解
自定义用户认证逻辑
处理用户信息获取逻辑:UserDetailsService
处理用户校验逻辑:UserDetails
处理密码加密解密:PasswordEncoder
认证处理流程
以表单认证为例:从发起认证请求到认证过滤器,接着认证成功后,响应从认证过滤器返回的整个过程。SpringSecurity做了什么,设计到了哪些类?他们之间如何调用?
SpringSecurity认证流程中涉及到的主要的类和接口如下:
- debug启动,浏览器登录:
2. 进入UsernamePasswordAuthenticationFilter:
3.AuthenticationManager
AuthenticationManager自身并不包含验证逻辑,用来管理AuthenticationProvider;
AuthenticationManager有很多实现类,主要是ProviderManager:
ProviderManager在public Authentication authenticate(Authentication authentication)会进行for循环,获取所有AuthenticationProvider,所有校验逻辑是在AuthenticationProvider里面的,为什么这里是一个集合:是因为不同的登录方式其认证逻辑是不一样的,我们现在是用户名密码登录,是需要去校验密码。如果是微信登录,则又是不一样的。AuthenticationManager作用就是把所有AuthenticationProvider搜集起来,认证时候,挨个去问,你当前的provider支不支持我现在的的登录方式(其实就是做循环,然后调用supports方法)。
4.AuthenticationProvider
进入DaoAuthenticationProvider,DaoAuthenticationProvider会调用其父类的AbstractUserDetailsAuthenticationProvider的authenticate方法
附加检查:在DaoAuthenticationProvider--->检查密码是否匹配
然后进行后检查:后检查主要检查4个boolean中最后一个:
如果上面检验全部没问题的话,就认为认证是合法的,然后我们再创建一个已经授权的Authentication
5.UserDetailsService
自定义用户认证,实现UserDetailsService接口
6.UserDetails
User是UserDetails实现类
7.登录成功之后走到我们自定义的成功处理器
进入AbstractAuthenticationProcessingFilter
进入自定义成功处理器:
认证结果在多个请求之间共享
多个请求共享肯定是放到session里,那么SpringSecurity是什么时候放到了session里面?什么时候又从session里面读取出来的?
AbstractAuthenticationProcessingFilter里面successfulAuthentication有一个:
SecurityContextHolder.getContext().setAuthentication(authResult);
其实是把认证成功的Authentication放到: SecurityContext里面,然后SecurityContext放到SecurityContextHolder里面。
SecurityContext其实很简单,他就是一个接口,其唯一的实现类是:
package org.springframework.security.core.context; import org.springframework.security.core.Authentication; public class SecurityContextImpl implements SecurityContext { private static final long serialVersionUID = 420L; private Authentication authentication; public SecurityContextImpl() { } public boolean equals(Object obj) { if (obj instanceof SecurityContextImpl) { SecurityContextImpl test = (SecurityContextImpl)obj; if (this.getAuthentication() == null && test.getAuthentication() == null) { return true; } if (this.getAuthentication() != null && test.getAuthentication() != null && this.getAuthentication().equals(test.getAuthentication())) { return true; } } return false; } public Authentication getAuthentication() { return this.authentication; } public int hashCode() { return this.authentication == null ? -1 : this.authentication.hashCode(); } public void setAuthentication(Authentication authentication) { this.authentication = authentication; } public String toString() { StringBuilder sb = new StringBuilder(); sb.append(super.toString()); if (this.authentication == null) { sb.append(": Null authentication"); } else { sb.append(": Authentication: ").append(this.authentication); } return sb.toString(); } }
SecurityContext说明:是对Authentication的包装,并且重写其hashCode和equals,保证其唯一性。
SecurityContextHolder:
实际上是ThreaadLocal封装,ThreadLocal是跟线程绑定的一个Map,在这个线程里面存放的东西可以在另一个线程读取出来,认证结果存入ThreaadLocalMap,可以让不同方法直接使用,避免重复认证,参数传递的麻烦。
最后:SecurityContextHolder会交给SecurityContextPersistenceFilter过滤器:他的位置在过滤器链的最前端:
SecurityContextPersistenceFilter过滤器作用:
- 当请求进来的时候:检查session里面是否有SecurityContext,如果有将其拿出来,然后放到线程里面,如果没有就过去了;响应时候:从线程里面(因为其在过滤器链上最前端,请求先经过此过滤器,然后最后会从这个过滤器出去)
- 当整个请求响应回来以后:最后一个过他的时候,他检查线程,如果线程里面有:SecurityContext,就拿出来放到session里面去。
这样不同的请求可以从线程里面拿到认证信息。拿到以后放到线程里面,因为请求和响应是在一个线程中。
获取认证用户信息
@GetMapping("/me") public Object getCurrentUser(){ Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); Object u = SecurityContextHolder.getContext().getAuthentication().getPrincipal(); return authentication; }
或参数中直接写Authentication authentication
@GetMapping("/me2") public Object getCurrentUser(Authentication authentication){//框架会注入 //Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); //Object u = SecurityContextHolder.getContext().getAuthentication().getPrincipal(); Object u = authentication.getPrincipal(); return authentication; }
只需要UserDetails
@GetMapping("/me3") public Object getCurrentUser(@AuthenticationPrincipal UserDetails userDetails){//框架会注入 String username = userDetails.getUsername(); Collection<? extends GrantedAuthority> authorities = userDetails.getAuthorities(); boolean accountNonExpired = userDetails.isAccountNonExpired(); return userDetails; }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· .NET10 - 预览版1新功能体验(一)