9.SpringSecurity核心过滤器-SecurityContextPersistenceFilter
SpringSecurity核心过滤器-SecurityContextPersistenceFilter
一、SpringSecurity中的核心组件
在SpringSecurity中的jar分为4个,作用分别为
| jar | 作用 |
|---|---|
| spring-security-core | SpringSecurity的核心jar包,认证和授权的核心代码都在这里面 |
| spring-security-config | 如果使用Spring Security XML名称空间进行配置或Spring Security的<br />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中<br />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 {@AutowiredUserMapper userMapper;/*** 根据账号密码验证的方法* @param username* @return* @throws UsernameNotFoundException*/@Overridepublic 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的运行关联我们就需要来看看SecurityContextPersistenceFilter的作用了
二、SecurityContextPersistenceFilter
首先在Session中维护一个用户的安全信息就是这个过滤器处理的。从request中获取session,从Session中取出已认证用户的信息保存在SecurityContext中,提高效率,避免每一次请求都要解析用户认证信息,方便接下来的filter直接获取当前的用户信息。
1.SecutiryContextRepository
SecutiryContextRepository接口非常简单,定义了对SecurityContext的存储操作,在该接口中定义了如下的几个方法
public interface SecurityContextRepository {/*** 获取SecurityContext对象*/SecurityContext loadContext(HttpRequestResponseHolder requestResponseHolder);/*** 存储SecurityContext*/void saveContext(SecurityContext context, HttpServletRequest request, HttpServletResponse response);/*** 判断是否存在SecurityContext对象*/boolean containsContext(HttpServletRequest request);}
默认的实现是HttpSessionSecurityContextRepository。也就是把SecurityContext存储在了HttpSession中。对应的抽象方法实现如下:
先来看看loadContext方法
public SecurityContext loadContext(HttpRequestResponseHolder requestResponseHolder) {// 获取对有的Request和Response对象HttpServletRequest request = requestResponseHolder.getRequest();HttpServletResponse response = requestResponseHolder.getResponse();// 获取HttpSession对象HttpSession httpSession = request.getSession(false);// 从HttpSession中获取SecurityContext对象SecurityContext context = readSecurityContextFromSession(httpSession);if (context == null) {// 如果HttpSession中不存在SecurityContext对象就创建一个// SecurityContextHolder.createEmptyContext();// 默认是ThreadLocalSecurityContextHolderStrategy存储在本地线程中context = generateNewContext();if (this.logger.isTraceEnabled()) {this.logger.trace(LogMessage.format("Created %s", context));}}// 包装Request和Response对象SaveToSessionResponseWrapper wrappedResponse = new SaveToSessionResponseWrapper(response, request,httpSession != null, context);requestResponseHolder.setResponse(wrappedResponse);requestResponseHolder.setRequest(new SaveToSessionRequestWrapper(request, wrappedResponse));return context;}
然后再来看看saveContext方法。
@Overridepublic void saveContext(SecurityContext context, HttpServletRequest request, HttpServletResponse response) {SaveContextOnUpdateOrErrorResponseWrapper responseWrapper = WebUtils.getNativeResponse(response,SaveContextOnUpdateOrErrorResponseWrapper.class);Assert.state(responseWrapper != null, () -> "Cannot invoke saveContext on response " + response+ ". You must use the HttpRequestResponseHolder.response after invoking loadContext");responseWrapper.saveContext(context);}
继续进入
@Overrideprotected void saveContext(SecurityContext context) {// 获取Authentication对象final Authentication authentication = context.getAuthentication();// 获取HttpSession对象HttpSession httpSession = this.request.getSession(false);// String springSecurityContextKey = HttpSessionSecurityContextRepository.this.springSecurityContextKey;// See SEC-776if (authentication == null|| HttpSessionSecurityContextRepository.this.trustResolver.isAnonymous(authentication)) {if (httpSession != null && this.authBeforeExecution != null) {// SEC-1587 A non-anonymous context may still be in the session// SEC-1735 remove if the contextBeforeExecution was not anonymoushttpSession.removeAttribute(springSecurityContextKey);this.isSaveContextInvoked = true;}if (this.logger.isDebugEnabled()) {if (authentication == null) {this.logger.debug("Did not store empty SecurityContext");}else {this.logger.debug("Did not store anonymous SecurityContext");}}return;}httpSession = (httpSession != null) ? httpSession : createNewSessionIfAllowed(context, authentication);// If HttpSession exists, store current SecurityContext but only if it has// actually changed in this thread (see SEC-37, SEC-1307, SEC-1528)if (httpSession != null) {// We may have a new session, so check also whether the context attribute// is set SEC-1561if (contextChanged(context) || httpSession.getAttribute(springSecurityContextKey) == null) {// HttpSession 中存储SecurityContexthttpSession.setAttribute(springSecurityContextKey, context);this.isSaveContextInvoked = true;if (this.logger.isDebugEnabled()) {this.logger.debug(LogMessage.format("Stored %s to HttpSession [%s]", context, httpSession));}}}}
最后就是containsContext方法
@Overridepublic boolean containsContext(HttpServletRequest request) {// 获取HttpSessionHttpSession session = request.getSession(false);if (session == null) {return false;}// 从session中能获取就返回true否则falsereturn session.getAttribute(this.springSecurityContextKey) != null;}
2.SecurityContextPersistenceFilter
然后我们来看看SecurityContextPersistenceFilter的具体处理逻辑
private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain)throws IOException, ServletException {// 同一个请求之处理一次if (request.getAttribute(FILTER_APPLIED) != null) {chain.doFilter(request, response);return;}// 更新状态request.setAttribute(FILTER_APPLIED, Boolean.TRUE);// 是否提前创建 HttpSessionif (this.forceEagerSessionCreation) {// 创建HttpSessionHttpSession session = request.getSession();if (this.logger.isDebugEnabled() && session.isNew()) {this.logger.debug(LogMessage.format("Created session %s eagerly", session.getId()));}}// 把Request和Response对象封装为HttpRequestResponseHolder对象HttpRequestResponseHolder holder = new HttpRequestResponseHolder(request, response);// 获取SecurityContext对象SecurityContext contextBeforeChainExecution = this.repo.loadContext(holder);try {// SecurityContextHolder绑定SecurityContext对象SecurityContextHolder.setContext(contextBeforeChainExecution);if (contextBeforeChainExecution.getAuthentication() == null) {logger.debug("Set SecurityContextHolder to empty SecurityContext");}else {if (this.logger.isDebugEnabled()) {this.logger.debug(LogMessage.format("Set SecurityContextHolder to %s", contextBeforeChainExecution));}}// 结束交给下一个过滤器处理chain.doFilter(holder.getRequest(), holder.getResponse());}finally {// 当其他过滤器都处理完成后SecurityContext contextAfterChainExecution = SecurityContextHolder.getContext();// 移除SecurityContextHolder中的SecuritySecurityContextHolder.clearContext();// 把this.repo.saveContext(contextAfterChainExecution, holder.getRequest(), holder.getResponse());// 存储Context在HttpSession中request.removeAttribute(FILTER_APPLIED);this.logger.debug("Cleared SecurityContextHolder to complete request");}}
通过上面的代码逻辑其实我们就清楚了在SpringSecurity中的认证信息的流转方式了。首先用户的认证状态Authentication是存储在SecurityContext中的,而每个用户的SecurityContext是统一存储在HttpSession中的。一次请求流转中我们需要获取当前的认证信息是通过SecurityContextHolder来获取的,默认是在ThreadLocal中存储的。

相关文章:
9.SpringSecurity核心过滤器-SecurityContextPersistenceFilter
SpringSecurity核心过滤器-SecurityContextPersistenceFilter 一、SpringSecurity中的核心组件 在SpringSecurity中的jar分为4个,作用分别为 jar作用spring-security-coreSpringSecurity的核心jar包,认证和授权的核心代码都在这里面spring-security-co…...
23种设计模式-桥接模式
概念 桥接模式是一种结构型设计模式,它通过将抽象与其实现分离来解耦。它使用接口(抽象类)作为桥梁,将一个抽象类与其实现类的代码分别独立开来,从而使它们可以各自独立地变化。桥接模式的核心思想是“组合优于继承”…...
TCP PMTU 静态路由
HTTP协议 --- 超文本传输协议TCP --- 80端口超文本 --- 包含有超链接link和多媒体元素标记的文本TCP协议是一种面向连接的可靠性传输协议面向连接:数据在传输前,收发双方建立一条逻辑通道。可靠性确认机制:传输确认,每接受一个数据…...
Android动画——属性动画
在属性动画中,常用到的API有ValueAnimator,ObjectAnimator。ValueAnimator:时间引擎,负责计算各个帧的属性值,基本上其他属性动画都会直接或间接继承它;ObjectAnimator: ValueAnimator 的子类&a…...
华为OD机试真题Python实现【寻找连续区间】真题+解题思路+代码(20222023)
寻找连续区间 题目 给定一个含有N个正整数的数组, 求出有多少个连续区间(包括单个正整数), 它们的和大于等于x。 🔥🔥🔥🔥🔥👉👉👉👉👉👉 华为OD机试(Python)真题目录汇总 ## 输入 第一行两个整数N x (0 < N <= 100000 ,0 <= x <=…...
15. 三数之和
给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i ! j、i ! k 且 j ! k ,同时还满足 nums[i] nums[j] nums[k] 0 。请 你返回所有和为 0 且不重复的三元组。 注意:答案中不可以包含重复的三元组。 示例 …...
40-Golang中的文件
Golang中的文件基本介绍文件的打开和关闭读文件操作应用实例写文件操作实例判断文件是否存在基本介绍 文件在程序中是以流的形式存在的 流:数据在数据源(文件)和程序(内存)之间经历的路程 输入流:数据从数据源到程序之间的路径 输出流:数据…...
Springboot整合RabbitMQ并使用
1、Springboot整合RabbitMQ 1、引入场景启动器 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-amqp</artifactId> </dependency>引入AMQP场景启动器之后,RabbitAutoConfiguratio…...
Java中方法引用(引用静态方法、引用成员方法(引用其他类的成员方法、引用本类的成员方法、引用父类的成员方法)、引用构造方法、其他调用方式、小练习)
方法引用:把已经存在的方法拿过来用,当作函数式接口中抽象方法的方法体 我们前面学到Arrays工具类中的sort方法,当我们需要指定排序规则时,需要传递Comparator接口的实现类对象,我们之前使用匿名内部类类的形式作为参…...
整理了100道关于Python基础知识的练习题,记得收藏~
实例1.数字组合 题目: 有四个数字:1、2、3、4,能组成多少个互不相同且无重复数字的三位数?各是多少? 程序分析: 遍历全部可能,把有重复的剃掉。 total0 for i in range(1,5):for j in range(…...
OSG三维渲染引擎编程学习之七十七:“第七章:OSG场景图形交互” 之 “7.8 场景交互”
目录 第七章 OSG场景图形交互 7.8 场景交互 7.8.1 osgGA库 7.8.2 事件消息处理 7.8.3 程序抓图示例...
797.差分
输入一个长度为 n的整数序列。 接下来输入 m个操作,每个操作包含三个整数 l,r,c,表示将序列中 [l,r] 之间的每个数加上 c。 请你输出进行完所有操作后的序列。 输入格式 第一行包含两个整数 n和 m。 第二行包含 n个整数,表示整数序列。 …...
为什么说要慎用BeanUtils,因为性能真的拉跨
1 背景之前在专栏中讲过“不推荐使用属性拷贝工具”,推荐直接定义转换类和方法使用 IDEA 插件自动填充 get / set 函数。不推荐的主要理由是:有些属性拷贝工具性能有点差有些属性拷贝工具有“BUG”使用属性拷贝工具容易存在一些隐患(后面例子…...
【项目设计】高并发内存池(六)[细节优化+测试]
🎇C学习历程:入门 博客主页:一起去看日落吗持续分享博主的C学习历程博主的能力有限,出现错误希望大家不吝赐教分享给大家一句我很喜欢的话: 也许你现在做的事情,暂时看不到成果,但不要忘记&…...
同模块设置不同应用主题方案
有时候公司内部会有不同应用但是有部分模块功能一样,只根据应用角色有些细节逻辑区分的场景。这时候往往采用模块化采用以应用至不同的APP。如果APP主题不一致,该如果解决。 方案: 在不同应用的config.gradle 下面根据不同应用定义不同的a…...
centos7 安装 hyperf
PHP > 7.4 Swoole PHP 扩展 > 4.5,并关闭了 Short Name OpenSSL PHP 扩展 JSON PHP 扩展 PDO PHP 扩展 Redis PHP 扩展 Protobuf PHP 扩展 composer create-project hyperf/hyperf-skeleton 推荐安装项 全部选n php.ini [swoole] extens…...
RZ/G2UL核心板-40℃低温启动测试
1. 测试对象HD-G2UL-EVM基于HD-G2UL-CORE工业级核心板设计,一路千兆网口、一路CAN-bus、 3路TTL UART、LCD、WiFi、CSI 摄像头接口等,接口丰富,适用于工业现场应用需求,亦方便用户评估核心板及CPU的性能。HD-G2UL-CORE系列工业级核…...
PyQt5可视化 7 饼图和柱状图实操案例 ①Qt项目的创建
目录 一、新建Qt项目 二、添加组件和布局 三、添加资源 1. 新建资源文件 2. 添加图标资源 四、frameHead 1. toolBtnGenData 2. toolBtnCounting 3. comboTheme 4. comboAnimation 5. Horizontal Spacer 6. toolBtnQuit 7. 设置toolBtnQuit的功能 8. frameHead的…...
0104路径搜索和单点路径-无向图-数据结构和算法(Java)
文章目录2 单点路径2.1 API2.2 算法实现后记2 单点路径 单点路径。给定一幅图和一个起点s,回答“从s到给定的目的顶点v是否存在一条路径?如果有,找出这条路径。”等类似问题。 2.1 API 单点路径问题在图的处理邻域中十分重要。根据标准设计…...
Maxscale读写分离实施文档
Maxscale介绍 MaxScale是maridb开发的一个mysql数据中间件,其配置简单,能够实现读写分离,并且可以根据主从状态实现写库的自动切换。 使用Maxscale无需对业务代码进行修改,其自带的读写分离模块,能够解析SQL语句&…...
深度学习在微纳光子学中的应用
深度学习在微纳光子学中的主要应用方向 深度学习与微纳光子学的结合主要集中在以下几个方向: 逆向设计 通过神经网络快速预测微纳结构的光学响应,替代传统耗时的数值模拟方法。例如设计超表面、光子晶体等结构。 特征提取与优化 从复杂的光学数据中自…...
如何在看板中体现优先级变化
在看板中有效体现优先级变化的关键措施包括:采用颜色或标签标识优先级、设置任务排序规则、使用独立的优先级列或泳道、结合自动化规则同步优先级变化、建立定期的优先级审查流程。其中,设置任务排序规则尤其重要,因为它让看板视觉上直观地体…...
深入理解JavaScript设计模式之单例模式
目录 什么是单例模式为什么需要单例模式常见应用场景包括 单例模式实现透明单例模式实现不透明单例模式用代理实现单例模式javaScript中的单例模式使用命名空间使用闭包封装私有变量 惰性单例通用的惰性单例 结语 什么是单例模式 单例模式(Singleton Pattern&#…...
STM32标准库-DMA直接存储器存取
文章目录 一、DMA1.1简介1.2存储器映像1.3DMA框图1.4DMA基本结构1.5DMA请求1.6数据宽度与对齐1.7数据转运DMA1.8ADC扫描模式DMA 二、数据转运DMA2.1接线图2.2代码2.3相关API 一、DMA 1.1简介 DMA(Direct Memory Access)直接存储器存取 DMA可以提供外设…...
《通信之道——从微积分到 5G》读书总结
第1章 绪 论 1.1 这是一本什么样的书 通信技术,说到底就是数学。 那些最基础、最本质的部分。 1.2 什么是通信 通信 发送方 接收方 承载信息的信号 解调出其中承载的信息 信息在发送方那里被加工成信号(调制) 把信息从信号中抽取出来&am…...
Cinnamon修改面板小工具图标
Cinnamon开始菜单-CSDN博客 设置模块都是做好的,比GNOME简单得多! 在 applet.js 里增加 const Settings imports.ui.settings;this.settings new Settings.AppletSettings(this, HTYMenusonichy, instance_id); this.settings.bind(menu-icon, menu…...
Typeerror: cannot read properties of undefined (reading ‘XXX‘)
最近需要在离线机器上运行软件,所以得把软件用docker打包起来,大部分功能都没问题,出了一个奇怪的事情。同样的代码,在本机上用vscode可以运行起来,但是打包之后在docker里出现了问题。使用的是dialog组件,…...
人工智能(大型语言模型 LLMs)对不同学科的影响以及由此产生的新学习方式
今天是关于AI如何在教学中增强学生的学习体验,我把重要信息标红了。人文学科的价值被低估了 ⬇️ 转型与必要性 人工智能正在深刻地改变教育,这并非炒作,而是已经发生的巨大变革。教育机构和教育者不能忽视它,试图简单地禁止学生使…...
群晖NAS如何在虚拟机创建飞牛NAS
套件中心下载安装Virtual Machine Manager 创建虚拟机 配置虚拟机 飞牛官网下载 https://iso.liveupdate.fnnas.com/x86_64/trim/fnos-0.9.2-863.iso 群晖NAS如何在虚拟机创建飞牛NAS - 个人信息分享...
【LeetCode】3309. 连接二进制表示可形成的最大数值(递归|回溯|位运算)
LeetCode 3309. 连接二进制表示可形成的最大数值(中等) 题目描述解题思路Java代码 题目描述 题目链接:LeetCode 3309. 连接二进制表示可形成的最大数值(中等) 给你一个长度为 3 的整数数组 nums。 现以某种顺序 连接…...
