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

SpringBoot3 - Spring Security 6.0 Migration

Spring Security 6.0 Migration

https://docs.spring.io/spring-security/reference/5.8/migration/servlet/config.html
最近在做SpringBoot2.x到3.0的升级。其中最主要的一部分是javax -> jakartapackageName的变更,另外一部分是对一些废弃/删除的类进行替换。大部分升级都比较顺利,但是在SpringSecurity上遇到了不少坑。

先看一下下面的代码

    @Beanpublic SecurityFilterChain filterChain(HttpSecurity http) throws Exception {ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry registry = http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and().csrf().disable().authorizeRequests().expressionHandler(webExpressionHandler());registry.antMatchers("/servlet1/**").permitAll().antMatchers("/servlet2/**").permitAll().antMatchers("/**").access("isLoggedIn()");return http.build();}

这段代码在6.0有两个问题,一是authorizeRequests标记为废弃,二是antMatchers方法被移除,这两个问题我们一个个看。

antMatchers -> requestMatchers

首先看一下antMatchers方法被移除的问题,随便搜一下就可以找到答案,使用requestMatchers来进行替代,看起来非常简单,替换之后我们启动server之后随便访问一下API,结果
在这里插入图片描述
随后又切换了几个发现全都是403,看log里也没有任何Error,怀疑是Spring进行了拦截,随后我们打开Trace级别的log,随后发现了如下信息
2023-03-22 15:44:32,746] [TRACE] 61259 [http-nio-8183-exec-2] edFilterInvocationSecurityMetadataSource - 59C004B425A34C0B96B01682C54B8B2C_1679471072599 - Did not match request to Mvc [pattern='/servlet1/**'] - [permitAll] (11/22)
我们的请求是GET localhost:8080/context/servlet1/test,怎么会不匹配呢?最后通过debug发现,在SpringSecurity6.0中,默认使用的是MvcRequestMatcher
它在匹配的时候会将context-pathservlet-path都去掉之后再进行匹配,拿上面的例子来说是用/test/servlet1/**进行匹配,匹配不上就会返回unauthorized 403。

由于我们项目中是多servlet的形式(历史原因)且API数量非常多,现在要去修改匹配路径非常容易漏掉某些API导致产线问题。然后我们仔细看了requestMatchers的方法内部

	public C requestMatchers(String... patterns) {return requestMatchers(null, patterns);}public C requestMatchers(HttpMethod method, String... patterns) {List<RequestMatcher> matchers = new ArrayList<>();if (mvcPresent) {matchers.addAll(createMvcMatchers(method, patterns));}else {matchers.addAll(RequestMatchers.antMatchers(method, patterns));}return requestMatchers(matchers.toArray(new RequestMatcher[0]));}

mvcPresent这个boolean是根据AbstractRequestMatcherRegistry.class存不存在来设值,这还是一个final变量想要通过修改mvcPresent的值来生成antMatchers似乎不现实,不过很快我们找到了另外一个方法

public C requestMatchers(RequestMatcher... requestMatchers) {Assert.state(!this.anyRequestConfigured, "Can't configure requestMatchers after anyRequest");return chainRequestMatchers(Arrays.asList(requestMatchers));}

这个方法允许我们传入任意一种RequestMatcher,RequestMatchers.antMatchers这个方法也可以为我们产生一个antMatchers,两者结合一下

```java@Beanpublic SecurityFilterChain filterChain(HttpSecurity http) throws Exception {ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry registry = http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and().csrf().disable().authorizeRequests().expressionHandler(webExpressionHandler());registry.requestMatchers(antMatchers("/servlet1/**")).permitAll().requestMatchers.(antMatchers("/servlet2/**")).permitAll().requestMatchers("/**").access("isLoggedIn()");return http.build();}

最后测试通过,问题解决!

authorizeRequests() -> authorizeHttpRequests()

authorizeRequests在6.0中被标记为废弃理论上暂时可以不进行处理,但是考虑到以后真正删除之后还要花时间重新再研究一遍,不如一鼓作气都处理掉了。我们先看看spring留下的注释

Deprecated
Use authorizeHttpRequests() instead

看起来非常简单,直接用authorizeHttpRequests()方法进行替代就行了。当我们使用新的方法时遇到了两个error

  • expressionHandler()不存在
  • access()方法不再接受String作为参数
    authorizeRequests()返回的是ExpressionUrlAuthorizationConfigurer.ExpressionInterceptUrlRegistry,而authorizeHttpRequests返回的是AuthorizeHttpRequestsConfigurer.AuthorizationManagerRequestMatcherRegistry,两者不同方法也不能完全替代。
    首先简单介绍一下这两个方法做了什么,expressionHandler()接受一个SecurityExpressionHandler作为参数,这个handler中有一个方法createSecurityExpressionRoot
	@Overrideprotected SecurityExpressionOperations createSecurityExpressionRoot(Authentication authentication,FilterInvocation fi) {WebSecurityExpressionRoot root = new WebSecurityExpressionRoot(authentication, fi);root.setPermissionEvaluator(getPermissionEvaluator());root.setTrustResolver(this.trustResolver);root.setRoleHierarchy(getRoleHierarchy());root.setDefaultRolePrefix(this.defaultRolePrefix);return root;}

createSecurityExpressionRoot的第一行需要创建一个WebSecurityExpressionRoot,这个root负责解析access(expression)中的expression表达式,在本例中我们自定义的WebSecurityExpressionRoot中有一个isLoggedIn()方法,该方法返回一个boolean用于authorization判断。看到这里大概就可以明白 expressionHandler()access()是配套使用的,一个负责设置expression表达式,一个负责解析表达式,主要的作用是判断request是否被授权。

然后我们看一下AuthorizationManagerRequestMatcherRegistry的access方法

public AuthorizationManagerRequestMatcherRegistry access(AuthorizationManager<RequestAuthorizationContext> manager) {Assert.notNull(manager, "manager cannot be null");return AuthorizeHttpRequestsConfigurer.this.addMapping(this.matchers, manager);}

该方法接收AuthorizationManager作为参数,我们再看一下AuthorizationManager的定义

/*** An Authorization manager which can determine if an {@link Authentication} has access to* a specific object.** @param <T> the type of object that the authorization check is being done one.* @author Evgeniy Cheban*/
@FunctionalInterface
public interface AuthorizationManager<T> {/*** Determines if access should be granted for a specific authentication and object.* @param authentication the {@link Supplier} of the {@link Authentication} to check* @param object the {@link T} object to check* @throws AccessDeniedException if access is not granted*/default void verify(Supplier<Authentication> authentication, T object) {AuthorizationDecision decision = check(authentication, object);if (decision != null && !decision.isGranted()) {throw new AccessDeniedException("Access Denied");}}/*** Determines if access is granted for a specific authentication and object.* @param authentication the {@link Supplier} of the {@link Authentication} to check* @param object the {@link T} object to check* @return an {@link AuthorizationDecision} or null if no decision could be made*/@NullableAuthorizationDecision check(Supplier<Authentication> authentication, T object);}

AuthorizationManager其实很简单,主要就是实现check方法来返回对应的AuthorizationDecision。至此我们似乎已经有了解决方案,我们可以自定一个AuthorizationManager,将isLoggedIn()的逻辑放入其中即可。不过在查看Spring Migration的文档的过程中,我们发现了一个有意思的类WebExpressionAuthorizationManager

	/*** Creates an instance.* @param expressionString the raw expression string to parse*/public WebExpressionAuthorizationManager(String expressionString) {Assert.hasText(expressionString, "expressionString cannot be empty");this.expression = this.expressionHandler.getExpressionParser().parseExpression(expressionString);}/*** Sets the {@link SecurityExpressionHandler} to be used. The default is* {@link DefaultHttpSecurityExpressionHandler}.* @param expressionHandler the {@link SecurityExpressionHandler} to use*/public void setExpressionHandler(SecurityExpressionHandler<RequestAuthorizationContext> expressionHandler) {Assert.notNull(expressionHandler, "expressionHandler cannot be null");this.expressionHandler = expressionHandler;this.expression = expressionHandler.getExpressionParser().parseExpression(this.expression.getExpressionString());}

WebExpressionAuthorizationManager的构造函数需要传入一个expression表达式,并提供了一个方法setExpressionHandler,这一切不正是我们所需要的!
需要注意的是,虽然都是SecurityExpressionHandler,但是泛型参数不同需要我们做些调整
最后,代码如下

@Beanpublic SecurityFilterChain filterChain(HttpSecurity http) throws Exception {var authorizationManager = new WebExpressionAuthorizationManager("isLoggedIn()");authorizationManager.setExpressionHandler(webExpressionHandler());ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry registry = http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and().csrf().disable().authorizeHttpRequests();registryregistry.requestMatchers(antMatchers("/servlet1/**")).permitAll().requestMatchers.(antMatchers("/servlet2/**")).permitAll().requestMatchers("/**").access(authorizationManager);return http.build();}

相关文章:

SpringBoot3 - Spring Security 6.0 Migration

Spring Security 6.0 Migration https://docs.spring.io/spring-security/reference/5.8/migration/servlet/config.html 最近在做SpringBoot2.x到3.0的升级。其中最主要的一部分是javax -> jakartapackageName的变更&#xff0c;另外一部分是对一些废弃/删除的类进行替换。…...

【新2023Q2模拟题JAVA】华为OD机试 - 最少停车数

最近更新的博客 华为od 2023 | 什么是华为od,od 薪资待遇,od机试题清单华为OD机试真题大全,用 Python 解华为机试题 | 机试宝典【华为OD机试】全流程解析+经验分享,题型分享,防作弊指南华为od机试,独家整理 已参加机试人员的实战技巧本篇题解:最少停车数 题目 特定大小的…...

《代码实例前端Vue》Security查询用户列表,用户新增

login.html <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>系统登录-超市订单管理系统</title><link rel"stylesheet" href"../css/style.css"><script type&qu…...

CANopenNode学习笔记(一)--- README翻译

CANopenNode学习笔记 文章目录CANopenNode学习笔记特性CANopen其他CANopenNode 流程图文件结构对象字典编辑器CANopenNode 是免费开源的CANopen协议栈。 CANopen是建立在CAN基础上的用于嵌入式控制系统的国际标准化(EN 50325-4) (CiA301)高层协议。有关CANopen的更多信息&#…...

关于Android 11、12和13服务保活问题

物联网环境&#xff0c;为了解决不同厂商、不同设备、不同网络情况下使用顺畅&#xff0c;同时也考虑到节约成本&#xff0c;缩小应用体积的好处&#xff0c;我们需要一个服务应用一直存在系统中&#xff0c;保活它以提供服务给其他客户端调用。 开机自启动&#xff0c;通过广播…...

Java 泛型 使用案例

参考资料 Java 基础 - 泛型机制详解路人甲-Java泛型专题 目录一. 通用Mapper1.1 实体类1.2 Mapper基类1.3 自定义接口1.4 抽象基类Service1.5 调用二. session和bean的获取一. 通用Mapper 1.1 实体类 ⏹ Accessors(chain true): 允许链式调用 import lombok.Data; import …...

进程与线程

文章目录什么是线程线程与进程的关系线程与进程的区别什么是线程 上一篇文章中我们介绍了什么进程&#xff0c;我们把进程比作一个工厂&#xff0c;那么线程就是工厂中的流水线。引入进程的目的就是为了实现多个任务并发执行&#xff0c;但是如果频繁的创建销毁进程&#xff0…...

骑友,怎么挑选适合自己的赛事

骑友&#xff0c;怎么挑选适合自己的赛事一、从场地、路况、天气&#xff0c;各个方面了解赛事的要求。二、看完赛事&#xff0c;要知道自己适合参加什么样的比赛。三、通过比赛成绩&#xff0c;对比自己的实力。四、综合考虑自己的经济能力&#xff0c;根据自己的经济能力选择…...

【Java 数据结构与算法】-遍历Map和Set的方式

作者&#xff1a;学Java的冬瓜 博客主页&#xff1a;☀冬瓜的主页&#x1f319; 专栏&#xff1a;【Java 数据结构与算法】 文章目录一、遍历Map法一 先获取Map集合的全部key的set集合&#xff0c;遍历map的key的Set集合法二 把map的key和value打包成Set的key后的这个Set集合法…...

组件、套件、 中间件、插件

组件、套件、 中间件、插件 组件 位于框架最底层&#xff0c;是由重复的代码提取出来合并而成。组件的本质&#xff0c;是一件产品&#xff0c;独立性很强&#xff0c;组件的核心&#xff0c;是复用&#xff0c;与其它功能又有强依赖关系。 模块 在中台产品和非中台产品中&…...

自动化工具 pytest 内核测试平台落地初体验

测试平台&#xff0c;有人说它鸡肋&#xff0c;有人说它有用&#xff0c;有人说它轮子&#xff0c;众说纷纭&#xff0c;不如从自身出发&#xff0c;考虑是否要做测试平台&#xff1a; 第 1 阶段&#xff0c;用 Pythonrequests 写接口自动化。 第 2 阶段&#xff0c;选择 unit…...

Python 自动化指南(繁琐工作自动化)第二版:四、列表

原文&#xff1a;https://automatetheboringstuff.com/2e/chapter4/ 在开始认真编写程序之前&#xff0c;您需要理解的另一个主题是列表数据类型及其表亲元组。列表和元组可以包含多个值&#xff0c;这使得编写处理大量数据的程序更加容易。由于列表本身可以包含其他列表&#…...

大数据领域的发展及其对现实世界的价值

大数据已经成为全球各行业领域不可或缺的一部分&#xff0c;并且其应用不断涌现。尽管很多人最初对“大数据”这一术语表示怀疑和不信任&#xff0c;但大数据技术已经确立了稳定的发展方向。根据调研机构的预测&#xff0c;到2027年&#xff0c;全球大数据市场规模将达到1090亿…...

几种常见的架构模式

本文已经收录到Github仓库&#xff0c;该仓库包含计算机基础、Java基础、多线程、JVM、数据库、Redis、Spring、Mybatis、SpringMVC、SpringBoot、分布式、微服务、设计模式、架构、校招社招分享等核心知识点&#xff0c;欢迎star~ Github地址 如果访问不了Github&#xff0c…...

flutter安装各种问题汇总

C:\Users\Administrator>flutter doctor -v Flutter assets will be downloaded from https://storage.flutter-io.cn. Make sure you trust this source! [√] Flutter (Channel stable, 3.7.0, on Microsoft Windows [版本 10.0.19044.1826], locale zh-CN) • Flutte…...

网络传输层

目录传输层再谈端口号端口号范围划分认识知名端口号netstatpidofUDP协议UDP协议端格式UDP的特点面向数据报UDP的缓冲区UDP使用注意事项使用udp协议 的应用层协议其它TCP协议TCP协议段格式如何理解链接如何理解三次握手如何理解四次挥手概念TIME_WAIT/CLOSE_WAITTCP策略确认应答…...

linux内核启动分析(二)

文章目录1. set_task_stack_end_magic2.smp_setup_processor_id3. debug_objects_early_init4. cgroup_init_early4.1 init_cgroup_root4.1.1 init_cgroup_housekeeping4.2 cgroup_init_subsys5. local_irq_disable5.1 raw_irqs_disabled5.2 raw_local_irq_disable5.3 trace_ha…...

『EasyNotice』.NET开源消息通知组件——快速实现邮件/钉钉告警通知

&#x1f4e3;读完这篇文章里你能收获到 傻瓜式扩展方法直接使用如何通过EasyNotice快速实现钉钉/邮件的通知发送感谢点赞收藏&#xff0c;避免下次找不到~ 文章目录一、EasyNotice1. 功能介绍2. 源码地址二、项目接入1. 发送邮件通知Step 1 : 安装包&#xff0c;通过Nuget安装…...

JVM垃圾回收算法

垃圾标记阶段 对象存活判断&#xff1a;在堆里存放着几乎所有的Java对象实例&#xff0c;在GC执行垃圾回收之前&#xff0c;首先需要区分出内存中哪些是存活对象&#xff0c;哪些是已经死亡的对象。只有被标记为己经死亡的对象&#xff0c;GC才会在执行垃圾回收时&#xff0c;…...

怎么看待ChatGPT封号这件事呢?

最近的ChatGPT大量封号&#xff0c;刷爆了全网&#xff0c;我的两个个人账号被封禁了&#xff0c;不知道大家最近有没有遇到相关的报错信息&#xff0c;要么就是检查你当前的浏览器配置&#xff0c;最后来一个access denied&#xff0c;要么直接就给你来一个当前的国家不支持。…...

eNSP-Cloud(实现本地电脑与eNSP内设备之间通信)

说明&#xff1a; 想象一下&#xff0c;你正在用eNSP搭建一个虚拟的网络世界&#xff0c;里面有虚拟的路由器、交换机、电脑&#xff08;PC&#xff09;等等。这些设备都在你的电脑里面“运行”&#xff0c;它们之间可以互相通信&#xff0c;就像一个封闭的小王国。 但是&#…...

TDengine 快速体验(Docker 镜像方式)

简介 TDengine 可以通过安装包、Docker 镜像 及云服务快速体验 TDengine 的功能&#xff0c;本节首先介绍如何通过 Docker 快速体验 TDengine&#xff0c;然后介绍如何在 Docker 环境下体验 TDengine 的写入和查询功能。如果你不熟悉 Docker&#xff0c;请使用 安装包的方式快…...

YSYX学习记录(八)

C语言&#xff0c;练习0&#xff1a; 先创建一个文件夹&#xff0c;我用的是物理机&#xff1a; 安装build-essential 练习1&#xff1a; 我注释掉了 #include <stdio.h> 出现下面错误 在你的文本编辑器中打开ex1文件&#xff0c;随机修改或删除一部分&#xff0c;之后…...

oracle与MySQL数据库之间数据同步的技术要点

Oracle与MySQL数据库之间的数据同步是一个涉及多个技术要点的复杂任务。由于Oracle和MySQL的架构差异&#xff0c;它们的数据同步要求既要保持数据的准确性和一致性&#xff0c;又要处理好性能问题。以下是一些主要的技术要点&#xff1a; 数据结构差异 数据类型差异&#xff…...

ABAP设计模式之---“简单设计原则(Simple Design)”

“Simple Design”&#xff08;简单设计&#xff09;是软件开发中的一个重要理念&#xff0c;倡导以最简单的方式实现软件功能&#xff0c;以确保代码清晰易懂、易维护&#xff0c;并在项目需求变化时能够快速适应。 其核心目标是避免复杂和过度设计&#xff0c;遵循“让事情保…...

多模态图像修复系统:基于深度学习的图片修复实现

多模态图像修复系统:基于深度学习的图片修复实现 1. 系统概述 本系统使用多模态大模型(Stable Diffusion Inpainting)实现图像修复功能,结合文本描述和图片输入,对指定区域进行内容修复。系统包含完整的数据处理、模型训练、推理部署流程。 import torch import numpy …...

day36-多路IO复用

一、基本概念 &#xff08;服务器多客户端模型&#xff09; 定义&#xff1a;单线程或单进程同时监测若干个文件描述符是否可以执行IO操作的能力 作用&#xff1a;应用程序通常需要处理来自多条事件流中的事件&#xff0c;比如我现在用的电脑&#xff0c;需要同时处理键盘鼠标…...

Unity UGUI Button事件流程

场景结构 测试代码 public class TestBtn : MonoBehaviour {void Start(){var btn GetComponent<Button>();btn.onClick.AddListener(OnClick);}private void OnClick(){Debug.Log("666");}}当添加事件时 // 实例化一个ButtonClickedEvent的事件 [Formerl…...

掌握 HTTP 请求:理解 cURL GET 语法

cURL 是一个强大的命令行工具&#xff0c;用于发送 HTTP 请求和与 Web 服务器交互。在 Web 开发和测试中&#xff0c;cURL 经常用于发送 GET 请求来获取服务器资源。本文将详细介绍 cURL GET 请求的语法和使用方法。 一、cURL 基本概念 cURL 是 "Client URL" 的缩写…...

windows系统MySQL安装文档

概览&#xff1a;本文讨论了MySQL的安装、使用过程中涉及的解压、配置、初始化、注册服务、启动、修改密码、登录、退出以及卸载等相关内容&#xff0c;为学习者提供全面的操作指导。关键要点包括&#xff1a; 解压 &#xff1a;下载完成后解压压缩包&#xff0c;得到MySQL 8.…...