spring — Spring Security 5.7与6.0差异性对比

1. spring security

Spring Security 是一个提供身份验证、授权和针对常见攻击保护的框架。 凭借对保护命令式和反应式应用程序的一流支持,它成为基于Spring的标准安全框架。

Spring Security 在最近几个版本中配置的写法都有一些变化,很多常见的方法都废弃了,并且将在未来的 Spring Security7 中移除,Spring Security5.7 开始(对应 Spring Boot2.7 开始)将重新梳理一下主要功能变化。以下是spring security的版本列表:
在这里插入图片描述

2. spring security 新增功能

2.1 WebSecurityConfigurerAdapter

  • gh-11923 - Remove WebSecurityConfigurerAdapter. Instead, create a SecurityFilterChain bean.

这一条更新改变了我们配置SpringSecurity的方式,WebSecurityConfigurerAdapter 过期了,在目前最新的 Spring Security6.1 中,这个类已经完全被移除了,作为替代,我们需要创建类型为SecurityFilterChain的bean。
在这里插入图片描述
以前WebSecurityConfigurerAdapter+HttpSecurity 的方式为:

@Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {@Overrideprotected void configure(HttpSecurity http) throws Exception {http.authorizeHttpRequests((authz) -> authz.anyRequest().authenticated()).httpBasic(withDefaults());}
}

SecurityFilterChain+HttpSecurity 新的写法为:

@Configuration
public class SecurityConfiguration {@Beanpublic SecurityFilterChain filterChain(HttpSecurity http) throws Exception {http.authorizeHttpRequests((authz) -> authz.anyRequest().authenticated()).httpBasic(withDefaults());return http.build();}}

以前WebSecurityConfigurerAdapter+WebSecurity 的方式为:

@Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {@Overridepublic void configure(WebSecurity web) {web.ignoring().antMatchers("/api1", "/api2");}
}

SecurityFilterChain+WebSecurity 新的写法为:

@Configuration
public class SecurityConfiguration {@Beanpublic WebSecurityCustomizer webSecurityCustomizer() {return (web) -> web.ignoring().antMatchers("/api1", "/api2");}
}

以前WebSecurityConfigurerAdapter配置AuthenticationManager是通过重写父类的方法来获取这个 Bean:

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {@Override@Beanpublic AuthenticationManager authenticationManagerBean() throws Exception {return super.authenticationManagerBean();}
}

6.0后则需要单独创建AuthenticationManager这个bean:

@Configuration
public class SecurityConfig {@AutowiredUserService userService;@BeanAuthenticationManager authenticationManager() {DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();daoAuthenticationProvider.setUserDetailsService(userService);ProviderManager pm = new ProviderManager(daoAuthenticationProvider);return pm;}
}

也可以从 HttpSecurity 中提取出来 AuthenticationManager:

@Configuration
public class SpringSecurityConfiguration {AuthenticationManager authenticationManager;@AutowiredUserDetailsService userDetailsService;@Beanpublic SecurityFilterChain filterChain(HttpSecurity http) throws Exception {AuthenticationManagerBuilder authenticationManagerBuilder = http.getSharedObject(AuthenticationManagerBuilder.class);authenticationManagerBuilder.userDetailsService(userDetailsService);authenticationManager = authenticationManagerBuilder.build();http.csrf().disable().cors().disable().authorizeHttpRequests().antMatchers("/api/v1/account/register", "/api/v1/account/auth").permitAll().anyRequest().authenticated().and().authenticationManager(authenticationManager).sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);return http.build();}
}

Spring Security 的底层实际上就是一堆过滤器,所以我们之前在 configure(HttpSecurity) 方法中的配置,实际上就是配置过滤器链。现在过滤器链的配置,我们通过提供一个 SecurityFilterChain Bean 来配置过滤器链,SecurityFilterChain 是一个接口,这个接口只有一个实现类 DefaultSecurityFilterChain,构建 DefaultSecurityFilterChain 的第一个参数是拦截规则,也就是哪些路径需要拦截,第二个参数则是过滤器链,如果给了一个空集合,不设置忽略的路径请求, Spring Security 会拦截下所有的请求,然后在一个空集合中走一圈就结束了,相当于不拦截任何请求。

2.2 使用 Lambda表达式

and() 方法将被移除:
在这里插入图片描述
从上面 and 方法的注释中小伙伴们可以看到,官方现在是在推动基于 Lambda 的配置来代替传统的链式配置。

2.3 自定义 JSON 登录

Spring Security 默认处理登录数据的过滤器是 UsernamePasswordAuthenticationFilter,在这个过滤器中,系统会通过 request.getParameter(this.passwordParameter) 的方式将用户名和密码读取出来,很明显这就要求前端传递参数的形式是 key-value。
步骤为:

  1. 首先我们获取请求头,根据请求头的类型来判断请求参数的格式。
  2. 如果是 JSON 格式的参数,就在 if 中进行处理,否则说明是 key-value 形式的参数,那么我们就调用父类的方法进行处理即可。JSON 格式的参数的处理逻辑和 key-value 的处理逻辑是一致的,唯一不同的是参数的提取方式不同而已。
  3. 最后,我们还需要对这个过滤器进行配置:
@Configuration
public class SecurityConfig {@AutowiredUserService userService;@BeanJsonLoginFilter jsonLoginFilter() {JsonLoginFilter filter = new JsonLoginFilter();filter.setAuthenticationSuccessHandler((req,resp,auth)->{resp.setContentType("application/json;charset=utf-8");PrintWriter out = resp.getWriter();//获取当前登录成功的用户对象User user = (User) auth.getPrincipal();user.setPassword(null);RespBean respBean = RespBean.ok("登录成功", user);out.write(new ObjectMapper().writeValueAsString(respBean));});filter.setAuthenticationFailureHandler((req,resp,e)->{resp.setContentType("application/json;charset=utf-8");PrintWriter out = resp.getWriter();RespBean respBean = RespBean.error("登录失败");if (e instanceof BadCredentialsException) {respBean.setMessage("用户名或者密码输入错误,登录失败");} else if (e instanceof DisabledException) {respBean.setMessage("账户被禁用,登录失败");} else if (e instanceof CredentialsExpiredException) {respBean.setMessage("密码过期,登录失败");} else if (e instanceof AccountExpiredException) {respBean.setMessage("账户过期,登录失败");} else if (e instanceof LockedException) {respBean.setMessage("账户被锁定,登录失败");}out.write(new ObjectMapper().writeValueAsString(respBean));});filter.setAuthenticationManager(authenticationManager());filter.setFilterProcessesUrl("/login");return filter;}@BeanAuthenticationManager authenticationManager() {DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();daoAuthenticationProvider.setUserDetailsService(userService);ProviderManager pm = new ProviderManager(daoAuthenticationProvider);return pm;}@BeanSecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {//开启过滤器的配置http.authorizeHttpRequests()//任意请求,都要认证之后才能访问.anyRequest().authenticated().and()//开启表单登录,开启之后,就会自动配置登录页面、登录接口等信息.formLogin()//和登录相关的 URL 地址都放行.permitAll().and()//关闭 csrf 保护机制,本质上就是从 Spring Security 过滤器链中移除了 CsrfFilter.csrf().disable();http.addFilterBefore(jsonLoginFilter(), UsernamePasswordAuthenticationFilter.class);return http.build();}}

这里就是配置一个 JsonLoginFilter 的 Bean,并将之添加到 Spring Security 过滤器链中即可。在 Spring Boot3 之前(Spring Security6 之前),上面这段代码就可以实现 JSON 登录了。
但是从 Spring Boot3 开始,则存在问题,具体表现就是:当你调用登录接口登录成功之后,再去访问系统中的其他页面,又会跳转回登录页面,说明访问登录之外的其他接口时,系统不知道你已经登录过了。

在 Spring Boot3 之前,Spring Security 过滤器链中有一个名为 SecurityContextPersistenceFilter 的过滤器,这个过滤器在 Spring Boot2.7.x 中废弃了,但是还在使用,在 Spring Boot3 中则被从 Spring Security 过滤器链中移除了,取而代之的是一个名为 SecurityContextHolderFilter 的过滤器。

先来看 SecurityContextPersistenceFilter 的核心逻辑:

private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain)throws IOException, ServletException {HttpRequestResponseHolder holder = new HttpRequestResponseHolder(request, response);SecurityContext contextBeforeChainExecution = this.repo.loadContext(holder);try {SecurityContextHolder.setContext(contextBeforeChainExecution);chain.doFilter(holder.getRequest(), holder.getResponse());}finally {SecurityContext contextAfterChainExecution = SecurityContextHolder.getContext();SecurityContextHolder.clearContext();this.repo.saveContext(contextAfterChainExecution, holder.getRequest(), holder.getResponse());}
}
  1. 首先,这个过滤器位于整个 Spring Security 过滤器链的第三个,是非常靠前的。
  2. 当登录请求经过这个过滤器的时候,首先会尝试从 SecurityContextRepository(上文中的 this.repo)中读取到 SecurityContext 对象,这个对象中保存了当前用户的信息,第一次登录的时候,这里实际上读取不到任何用户信息。
  3. 将读取到的 SecurityContext 存入到 SecurityContextHolder 中,默认情况下,SecurityContextHolder 中通过 ThreadLocal 来保存 SecurityContext 对象,也就是当前请求在后续的处理流程中,只要在同一个线程里,都可以直接从 SecurityContextHolder 中提取到当前登录用户信息。
  4. 请求继续向后执行。
  5. 在 finally 代码块中,当前请求已经结束了,此时再次获取到 SecurityContext,并清空 SecurityContextHolder 防止内存泄漏
  6. 然后调用 this.repo.saveContext 方法保存当前登录用户对象(实际上是保存到 HttpSession 中)
  7. 以后其他请求到达的时候,执行前面第 2 步的时候,就读取到当前用户的信息了,在请求后续的处理过程中,Spring Security 需要知道当前用户的时候,会自动去 SecurityContextHolder 中读取当前用户信息。

这就是 Spring Security 认证的一个大致流程。

然而,到了 Spring Boot3 之后,这个过滤器被 SecurityContextHolderFilter 取代了,我们来看下 SecurityContextHolderFilter 过滤器的一个关键逻辑:

private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain)throws ServletException, IOException {Supplier<SecurityContext> deferredContext = this.securityContextRepository.loadDeferredContext(request);try {this.securityContextHolderStrategy.setDeferredContext(deferredContext);chain.doFilter(request, response);}finally {this.securityContextHolderStrategy.clearContext();request.removeAttribute(FILTER_APPLIED);}
}

通过这段逻辑代码我们发现前面的逻辑基本上还是一样的,不一样的是 finally 中的代码,finally 中少了一步向 HttpSession 保存 SecurityContext 的操作。
未处理加入SecurityContextHolderFilter过滤器之前用户登录成功后,用户信息没有保存到 HttpSession,导致下一次请求到达的时候,无法从 HttpSession 中读取到 SecurityContext 存到 SecurityContextHolder 中,在后续的执行过程中,Spring Security 就会认为当前用户没有登录。这就是问题的原因!

问题解决:

Spring Security 提供了一个修改的入口,在 org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter#successfulAuthentication 方法中,源码如下:

protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain,Authentication authResult) throws IOException, ServletException {SecurityContext context = this.securityContextHolderStrategy.createEmptyContext();context.setAuthentication(authResult);this.securityContextHolderStrategy.setContext(context);this.securityContextRepository.saveContext(context, request, response);this.rememberMeServices.loginSuccess(request, response, authResult);if (this.eventPublisher != null) {this.eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(authResult, this.getClass()));}this.successHandler.onAuthenticationSuccess(request, response, authResult);
}

这个方法是当前用户登录成功之后的回调方法,小伙伴们看到,在这个回调方法中,有一句 this.securityContextRepository.saveContext(context, request, response);,这就表示将当前登录成功的用户信息存入到 HttpSession 中。

在当前过滤器中,securityContextRepository 的类型是 RequestAttributeSecurityContextRepository,这个表示将 SecurityContext 存入到当前请求的属性中,那很明显,在当前请求结束之后,这个数据就没了。在 Spring Security 的自动化配置类中,将 securityContextRepository 属性指向了 DelegatingSecurityContextRepository,这是一个代理的存储器,代理的对象是 RequestAttributeSecurityContextRepository 和 HttpSessionSecurityContextRepository,所以在默认的情况下,用户登录成功之后,在这里就把登录用户数据存入到 HttpSessionSecurityContextRepository 中了。

当我们自定义了登录过滤器之后,就破坏了自动化配置里的方案了,这里使用的 securityContextRepository 对象就真的是 RequestAttributeSecurityContextRepository 了,所以就导致用户后续访问时系统以为用户未登录。

那么解决方案很简单,我们只需要为自定义的过滤器指定 securityContextRepository 属性的值就可以了,如下:

@Bean
JsonLoginFilter jsonLoginFilter() {JsonLoginFilter filter = new JsonLoginFilter();filter.setAuthenticationSuccessHandler((req,resp,auth)->{resp.setContentType("application/json;charset=utf-8");PrintWriter out = resp.getWriter();//获取当前登录成功的用户对象User user = (User) auth.getPrincipal();user.setPassword(null);RespBean respBean = RespBean.ok("登录成功", user);out.write(new ObjectMapper().writeValueAsString(respBean));});filter.setAuthenticationFailureHandler((req,resp,e)->{resp.setContentType("application/json;charset=utf-8");PrintWriter out = resp.getWriter();RespBean respBean = RespBean.error("登录失败");if (e instanceof BadCredentialsException) {respBean.setMessage("用户名或者密码输入错误,登录失败");} else if (e instanceof DisabledException) {respBean.setMessage("账户被禁用,登录失败");} else if (e instanceof CredentialsExpiredException) {respBean.setMessage("密码过期,登录失败");} else if (e instanceof AccountExpiredException) {respBean.setMessage("账户过期,登录失败");} else if (e instanceof LockedException) {respBean.setMessage("账户被锁定,登录失败");}out.write(new ObjectMapper().writeValueAsString(respBean));});filter.setAuthenticationManager(authenticationManager());filter.setFilterProcessesUrl("/login");filter.setSecurityContextRepository(new HttpSessionSecurityContextRepository());return filter;
}

最后调用 setSecurityContextRepository 方法设置一下就行。Spring Boot3.x 之前之所以不用设置这个属性,是因为这里虽然没保存最后还是在 SecurityContextPersistenceFilter 过滤器中保存了。

在登录成功之后,开发者自己手动将数据存入到 HttpSession 中,这样就能确保下个请求到达的时候,能够从 HttpSession 中读取到有效的数据存入到 SecurityContextHolder 中。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/20867.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

宇凡微2.4g遥控船开发方案,采用合封芯片

2.4GHz遥控船的开发方案是一个有趣且具有挑战性的项目。这样的遥控船可以通过无线2.4GHz频率进行远程控制&#xff0c;让用户在池塘或湖泊上畅游。以下是一个简要的2.4GHz遥控船开发方案&#xff1a; 基本构想如下 mcu驱动两个小电机&#xff0c;小电机上安装两个螺旋桨&#…

在使用Python爬虫时遇到503 Service Unavailable错误解决办法汇总

在进行Python爬虫的过程中&#xff0c;有时会遇到503 Service Unavailable错误&#xff0c;这意味着所请求的服务不可用&#xff0c;无法获取所需的数据。为了解决这个常见的问题&#xff0c;本文将提供一些解决办法&#xff0c;希望能提供实战价值&#xff0c;让爬虫任务顺利完…

Docker中gitlab以及gitlab-runner的安装与使用

1、本文主要讲述如何使用Docker安装gitlab以及gitlab-runner&#xff0c;并且会讲述gitlab-runner如何使用 2、gitlab部分不需要修改过多的配置即可使用&#xff0c;本文未讲述https配置&#xff0c;如有需求&#xff0c;可自行百度 3、Docker如何安装可以自行百度 一、Docker安…

轻量化YOLOv5改进 | 结合repghost结构冲参数化网络,实现轻量化和加速推理,

RepGhost: A Hardware-Efficient Ghost Module via Re-parameterization 论文总结本文改进repghost 核心代码测试参数量和计算量🔥🔥🔥 “引入RepGhostNet以加速CNN网络推理” “网络宽度的自定义调整:无缝嵌入YOLOv5” “通过结构重参数化优化网络性能” “实现高效和…

【JVM】(二)深入理解Java类加载机制与双亲委派模型

文章目录 前言一、类加载过程1.1 加载&#xff08;Loading&#xff09;1.2 验证&#xff08;Verification&#xff09;1.3 准备&#xff08;Preparation&#xff09;1.4 解析&#xff08;Resolution&#xff09;1.5 初始化&#xff08;Initialization&#xff09; 二、双亲委派…

数字化采购:提升效率、优化供应链的新趋势

随着信息技术的快速发展&#xff0c;数字化采购正成为企业追求效率和优化供应链的新趋势。数字化采购是利用数字技术和互联网平台&#xff0c;实现采购流程的自动化和在线化。本文将围绕数字化采购的应用场景&#xff0c;探讨其在采购环节中带来的效益与优势。 一、在线供应商…

分布式限流方案及实现

优质博文&#xff1a;IT-BLOG-CN 一、限流的作用和意义 限流是对高并发访问进行限制&#xff0c;限速的过程。通过限流来限制资源&#xff0c;可以提高系统的稳定性和可靠性&#xff0c;控制系统的负载&#xff0c;削峰填谷&#xff0c;保证服务质量。 服务限流后的常见处理…

重磅!EBImage包:为何如此火爆?它的图像处理到底有何不可思议之处?

一、简介 1.1 EBImage包简介 EBImage包是一个广受欢迎的用于图像处理和分析的R语言包。它提供了一套全面而强大的功能&#xff0c;支持多种图像格式的读取和写入&#xff0c;处理多维图像数据&#xff0c;并提供了各种先进的图像处理算法、特征提取和测量函数。 1.2 EBImage爆火…

DAY1,C高级(命令,Linux的文件系统,软、硬链接文件)

1.创建链接文件&#xff1b; 文件系统中的每个文件都与唯一的 inode 相关联&#xff0c;inode 存储了文件的元数据和数据块的地址&#xff0c;文件名与 inode 之间的链接关系称为硬链接或软链接。 硬链接文件的创建&#xff1a; ln 被链接文件的绝对路径 硬链接文件的绝对…

nsqd的架构及源码分析

文章目录 一 nsq的整体代码结构 二 回顾nsq的整体架构图 三 nsqd进程的作用 四 nsqd启动流程的源码分析 五 本篇博客总结 在博客 nsq整体架构及各个部件作用详解_YZF_Kevin的博客-CSDN博客 中我们讲了nsq的整体框架&#xff0c;各个部件的大致作用。如果没看过的&…

论文笔记——Influence Maximization in Undirected Networks

Influence Maximization in Undirected Networks ContributionMotivationPreliminariesNotations Main resultsReduction to Balanced Optimal InstancesProving Theorem 3.1 for Balanced Optimal Instances Contribution 好久没发paper笔记了&#xff0c;这篇比较偏理论&…

pytorch 中 view 和reshape的区别

在 PyTorch&#xff08;一个流行的深度学习框架&#xff09;中&#xff0c; reshape 和 view 都是用于改变张量&#xff08;tensor&#xff09;形状的方法&#xff0c;但它们在实现方式和使用上有一些区别。下面是它们之间的主要区别&#xff1a; 实现方式&#xff1a; reshap…

html学习7(iframe)

1、通过使用iframe标签定义框架&#xff0c;可在同一个浏览器中显示不止一个画面。 2、height和width属性用于定义框架的高度与宽度。 3、属性frameborder‘0’用于是否显示边框。 4、iframe可以显示一个目标链接的页面&#xff0c;链接的target属性设置为相应的iframe名称。…

2023年华数杯C题思路

c题 母亲身心健康对婴儿成长的影响 母亲是婴儿生命中最重要的人之一,她不仅为婴儿提供营养物质和身体保护,还为婴儿提供情感支持和安全感。母亲心理健康状态的不良状况&#xff0c;如抑郁、焦虑压力等&#xff0c;可能会对婴儿的认知、情感、社会行为等方面产生负面影响。压力…

2023年华数杯C题详细思路

2023年华数杯作为与国赛同频的比赛&#xff08;都是周四6点发题&#xff0c;周日晚8点交卷&#xff09;&#xff0c;也是暑期唯一一个正式比赛。今年的报名队伍已经高达6000多对。基于这么多的人数进行国赛前队伍的练习&#xff0c;以及其他用途。为了方便大家跟更好的选题&…

机器学习03-数据理解(小白快速理解分析Pima Indians数据集)

机器学习数据理解是指对数据集进行详细的分析和探索&#xff0c;以了解数据的结构、特征、分布和质量。数据理解是进行机器学习项目的重要第一步&#xff0c;它有助于我们对数据的基本属性有全面的了解&#xff0c;并为后续的数据预处理、特征工程和模型选择提供指导。 数据理解…

vue 图片回显标签

第一种 <el-form-item label"打款银行回单"><image-preview :src"form.bankreceiptUrl" :width"120" :height"120"/></el-form-item>// 值为 https://t11.baidu.com/it/app106&fJPEG&fm30&fmtauto&…

SpringBoot整合Caffeine

一、Caffeine介绍 1、缓存介绍 缓存(Cache)在代码世界中无处不在。从底层的CPU多级缓存&#xff0c;到客户端的页面缓存&#xff0c;处处都存在着缓存的身影。缓存从本质上来说&#xff0c;是一种空间换时间的手段&#xff0c;通过对数据进行一定的空间安排&#xff0c;使得下…

如何使用免费敏捷工具Leangoo领歌管理Sprint Backlog

什么是Sprint Backlog&#xff1f; Sprint Backlog是Scrum的主要工件之一。在Scrum中&#xff0c;团队按照迭代的方式工作&#xff0c;每个迭代称为一个Sprint。在Sprint开始之前&#xff0c;PO会准备好产品Backlog&#xff0c;准备好的产品Backlog应该是经过梳理、估算和优先…

JVM调优工具详解以及实战

准备 事先启动一个web应用程序&#xff0c;用jps查看进程id&#xff0c;接着用各种jdk自带的命令优化应用 Jmap jmap -histo 6160 #查看历史生成的实例 jmap -histo:live 6160 #查看当前存活的实例&#xff0c;执行过程中可能会触发一次full gc jmap -histo:live 6160 &…