当前位置: 首页 > news >正文

SpringSecurity源码初探

一、SpringSecurity中的核心组件

在SpringSecurity中的jar分为4个,作用分别为

jar作用
spring-security-coreSpringSecurity的核心jar包,认证和授权的核心代码都在这里面
spring-security-config如果使用Spring Security XML名称空间进行配置或Spring Security的 Java configuration支持,则需要它
spring-security-web用于Spring Security web身份验证服务和基于url的访问控制
spring-security-test测试单元

1.SecurityContextHolder

首先来看看在spring-security-core中的SecurityContextHolder,这个是一个非常基础的对象,存储了当前应用的上下文SecurityContext,而在SecurityContext可以获取Authentication对象。也就是当前认证的相关信息会存储在Authentication对象中。

默认情况下,SecurityContextHolder是通过ThreadLocal来存储对应的信息的。也就是在一个线程中我们可以通过这种方式来获取当前登录的用户的相关信息。而在SecurityContext中就只提供了对Authentication对象操作的方法

public interface SecurityContext extends Serializable { ​ Authentication getAuthentication(); ​ void setAuthentication(Authentication authentication); ​ }

xxxStrategy的各种实现

策略实现说明
GlobalSecurityContextHolderStrategy把SecurityContext存储为static变量
InheritableThreadLocalSecurityContextStrategy把SecurityContext存储在InheritableThreadLocal中 InheritableThreadLocal解决父线程生成的变量传递到子线程中进行使用
ThreadLocalSecurityContextStrategy把SecurityContext存储在ThreadLocal中

2.Authentication

Authentication是一个认证对象。在Authentication接口中声明了如下的相关方法。

public interface Authentication extends Principal, Serializable { ​ // 获取认证用户拥有的对应的权限 Collection<? extends GrantedAuthority> getAuthorities(); ​ // 获取哦凭证 Object getCredentials(); ​ // 存储有关身份验证请求的其他详细信息。这些可能是 IP地址、证书编号等 Object getDetails(); ​ // 获取用户信息 通常是 UserDetails 对象 Object getPrincipal(); ​ // 是否认证 boolean isAuthenticated(); ​ // 设置认证状态 void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException; ​ }

基于上面讲解的三者的关系我们在项目中如此来获取当前登录的用户信息了。

public String hello(){ Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); Object principal = authentication.getPrincipal(); if(principal instanceof UserDetails){ UserDetails userDetails = (UserDetails) principal; System.out.println(userDetails.getUsername()); return "当前登录的账号是:" + userDetails.getUsername(); } return "当前登录的账号-->" + principal.toString(); }

调用getContext()返回的对象是SecurityContext接口的一个实例,这个对象就是保存在线程中的。接下来将看到,Spring Security中的认证大都返回一个UserDetails的实例作为principa。

3.UserDetailsService

在上面的关系中我们看到在Authentication中存储当前登录用户的是Principal对象,而通常情况下Principal对象可以转换为UserDetails对象。UserDetails是Spring Security中的一个核心接口。它表示一个principal,但是是可扩展的、特定于应用的。可以认为UserDetails是数据库中用户表记录和Spring Security在SecurityContextHolder中所必须信息的适配器。

public interface UserDetails extends Serializable { ​ // 对应的权限 Collection<? extends GrantedAuthority> getAuthorities(); ​ // 密码 String getPassword(); ​ // 账号 String getUsername(); ​ // 账号是否过期 boolean isAccountNonExpired(); ​ // 是否锁定 boolean isAccountNonLocked(); ​ // 凭证是否过期 boolean isCredentialsNonExpired(); ​ // 账号是否可用 boolean isEnabled(); ​ }

而这个接口的默认实现就是User

那么这个UserDetails对象什么时候提供呢?其实在我们前面介绍的数据库认证的Service中我们就用到了,有一个特殊接口UserDetailsService,在这个接口中定义了一个loadUserByUsername的方法,接收一个用户名,来实现根据账号的查询操作,返回的是一个UserDetails对象。

public interface UserDetailsService { ​ UserDetails loadUserByUsername(String username) throws UsernameNotFoundException; ​ }

UserDetailsService接口的实现有如下:

Spring Security提供了许多UserDetailsSerivice接口的实现,包括使用内存中map的实现(InMemoryDaoImpl低版本 InMemoryUserDetailsManager)和使用JDBC的实现(JdbcDaoImpl)。但在实际开发中我们更喜欢自己来编写,比如UserServiceImpl我们的案例

/** * 用户的Service */ public interface UserService extends UserDetailsService { ​ } ​ /** * UserService接口的实现类 */ @Service public class UserServiceImpl implements UserService { ​ @Autowired UserMapper userMapper; ​ /** * 根据账号密码验证的方法 * @param username * @return * @throws UsernameNotFoundException */ @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { SysUser user = userMapper.queryByUserName(username); System.out.println("---------->"+user); if(user != null){ // 账号对应的权限 List<SimpleGrantedAuthority> authorities = new ArrayList<>(); authorities.add(new SimpleGrantedAuthority("ROLE_USER")); // 说明账号存在 {noop} 非加密的使用 UserDetails details = new User(user.getUserName() ,user.getPassword() ,true ,true ,true ,true ,authorities); return details; } throw new UsernameNotFoundException("账号不存在..."); ​ } }

4.GrantedAuthority

我们在Authentication中看到不光关联了Principal还提供了一个getAuthorities()方法来获取对应的GrantedAuthority对象数组。和权限相关,后面在权限模块详细讲解

public interface GrantedAuthority extends Serializable { ​ ​ String getAuthority(); ​ }

上面介绍到的核心对象小结:

核心对象作用
SecurityContextHolder用于获取SecurityContext
SecurityContext存放了Authentication和特定于请求的安全信息
Authentication特定于Spring Security的principal
GrantedAuthority对某个principal的应用范围内的授权许可
UserDetail提供从应用程序的DAO或其他安全数据源构建Authentication对象所需的信息
UserDetailsService接受String类型的用户名,创建并返回UserDetail

有了这块的基础我们可以来看看认证的实现流程了

二、认证流程

接下来我们直接来看看SpringSecurity中是如何处理认证操作的。

  • 1.账号验证

  • 2.密码验证

  • 3.记住我-->cookie信息

  • 4.登录成功-->跳转

1.UsernamePasswordAuthenticationFilter

在SpringSecurity中处理认证逻辑是在UsernamePasswordAuthenticationFilter这个过滤器中实现的。至于这个过滤器是怎么执行的,我们后面会详细的讲解,UsernamePasswordAuthenticationFilter继承于AbstractAuthenticationProcessingFilter这个父类。

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

上面的核心代码是

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

2.AuthenticationManager

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

public interface AuthenticationManager { Authentication authenticate(Authentication authentication) throws AuthenticationException; }

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

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

进入到AbstractUserDetailsAuthenticationProvider中的认证方法

然后进入到retrieveUser方法中,具体的实现是DaoAuthenticationProvider

@Override protected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException { prepareTimingAttackProtection(); try { // getUserDetailsService会获取到我们自定义的UserServiceImpl对象,也就是会走我们自定义的认证方法了 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); } }

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

然后就是具体的密码验证

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); }

如果有加密处理,就选择对应的加密对象来处理,比如我们上面使用的BCryptPasswordEncoder来处理

http://www.jsqmd.com/news/963964/

相关文章:

  • 实战vue3项目,用快马ai生成团队统一的vscode开发环境配置包
  • 卡片超量=流量归零?CSDN AI营销系统底层规则拆解,第4张起触发降权机制!
  • AI辅助开发:让快马智能优化你的tokenpocket钱包交互与状态管理代码
  • Notepad2-mod:轻量级文本编辑器的终极解决方案
  • 框架的核心角色
  • 大语言模型辅助智能合约静态审计:利用 AST 语法树解析与 LLM 提示词链漏洞扫描实战
  • 新手入门:基于快马平台生成第一个potplayer字幕翻译脚本
  • 2026年工衣/防静电工衣/电子厂工衣/食品厂工衣/夏天工衣供应厂家分析:透气舒适与安全防护双优之选 - 品牌企业推荐师(官方)
  • YOLO26红外小目标检测实战:缝合DASI模块,实现暗光环境下的特征极速增强
  • QGC地面站视频流拉不通?别急,先用这5个排查步骤搞定(从Ping到VLC播放器)
  • 3大核心功能彻底改变你的B站桌面体验
  • 普宁月子中心口碑排名|从月嫂、月子餐、修复三维怎么评 - 品牌观察
  • 跨学科研究新思路:怎么用 GPT-5.5 寻找不同领域之间的学术交叉点?(附实战教程)
  • Android应用保活技术突破:基于Linux特性的永生方案实现
  • 实战应用:基于js深入浅出vue理念,在快马平台快速构建博客后台管理系统
  • 嵌入式开发模块化编程实战:从Keil软仿真到工程架构设计
  • 2026指针电压表行业:解读三大核心发展趋势 - 资讯速览
  • AI辅助开发体验:借助快马智能模型构建漫画链接智能推荐系统
  • 智微JM系列桥接芯片选型、设计与实战指南
  • Vidupe:智能视频去重工具,彻底解决重复视频存储问题
  • Hermes+Obsidian打造终身可用的AI知识库
  • 零基础策划:如何用 GPT-5.5 在 5 分钟内写出商业活动策划案?(附大模型选型表)
  • 进入2026年,餐饮行业的数字化转型已从简单的“在线点餐”进化到了“全感知智能化管理”阶段。对于消费者和经营者普遍关心的核心痛点
  • 合肥矮小症哪个医院靠谱
  • 家里闲置黄金怎么处置?从经营模式看清杭州回收门店优劣 - 奢侈品回收评测
  • Beyond Compare 5终极激活指南:三步实现完整密钥生成与高效配置
  • 组件库工程底座:基于 TypeScript + Rollup 的多端通用(ESM/CommonJS)高质量组件打包体系搭建
  • 终极宝可梦随机化工具:Universal Pokemon Randomizer ZX 完整指南
  • 公司电话号码认证服务商哪家好?2026最新实力推荐 - 企业服务推荐
  • 【学术干货】 | 22TB数据集破解“光线骗局“——3DReflecNet:首个面向反光/透明物体的3D重建数据集