Spring Security6 全新写法,大变样!

Spring Security 在最近几个版本中配置的写法都有一些变化,很多常见的方法都废弃了,并且将在未来的 Spring Security7 中移除,因此松哥在去年旧文的基础之上,又补充了一些新的内容,重新发一下,供各位使用 Spring Security 的小伙伴们参考。,接下来,我把从 Spring Security5.7 开始(对应 Spring Boot2.7 开始),各种已知的变化都来和小伙伴们梳理一下。,图片图片,首先第一点,就是各位小伙伴最容易发现的 WebSecurityConfigurerAdapter 过期了,在目前最新的 Spring Security6.1 中,这个类已经完全被移除了,想凑合着用都不行了。,准确来说,Spring Security 是在 5.7.0-M2 这个版本中将 WebSecurityConfigurerAdapter 过期的,过期的原因是因为官方想要鼓励各位开发者使用基于组件的安全配置。,那么什么是基于组件的安全配置呢?我们来举几个例子:,以前我们配置 SecurityFilterChain 的方式是下面这样:,那么以后就要改为下面这样了:,如果懂之前的写法的话,下面这个代码其实是很好理解的,我就不做过多解释了,不过还不懂 Spring Security 基本用法的小伙伴,可以在公众号后台回复 ss,有松哥写的教程。,以前我们配置 WebSecurity 是这样:,以后就得改成下面这样了:,另外还有一个就是关于 AuthenticationManager  的获取,以前可以通过重写父类的方法来获取这个 Bean,类似下面这样:,以后就只能自己创建这个 Bean 了,类似下面这样:,当然,也可以从 HttpSecurity 中提取出来 AuthenticationManager,如下:,这也是一种办法。,我们来看一个具体的例子。,首先我们新建一个 Spring Boot 工程,引入 Web 和 Spring Security 依赖,注意 Spring Boot 选择最新版。,接下来我们提供一个简单的测试接口,如下:,小伙伴们知道,在 Spring Security 中,默认情况下,只要添加了依赖,我们项目的所有接口就已经被统统保护起来了,现在启动项目,访问 /hello 接口,就需要登录之后才可以访问,登录的用户名是 user,密码则是随机生成的,在项目的启动日志中。,现在我们的第一个需求是使用自定义的用户,而不是系统默认提供的,这个简单,我们只需要向 Spring 容器中注册一个 UserDetailsService 的实例即可,像下面这样:,这就可以了。,当然我现在的用户是存在内存中的,如果你的用户是存在数据库中,那么只需要提供 UserDetailsService 接口的实现类并注入 Spring 容器即可,这个之前在 vhr 视频中讲过多次了(公号后台回复 666 有视频介绍),这里就不再赘述了。,但是假如说我希望 /hello 这个接口能够匿名访问,并且我希望这个匿名访问还不经过 Spring Security 过滤器链,要是在以前,我们可以重写 configure(WebSecurity) 方法进行配置,但是现在,得换一种玩法:,以前位于 configure(WebSecurity) 方法中的内容,现在位于 WebSecurityCustomizer Bean 中,该配置的东西写在这里就可以了。,那如果我还希望对登录页面,参数等,进行定制呢?继续往下看:,Spring Security 的底层实际上就是一堆过滤器,所以我们之前在 configure(HttpSecurity) 方法中的配置,实际上就是配置过滤器链。现在过滤器链的配置,我们通过提供一个 SecurityFilterChain Bean 来配置过滤器链,SecurityFilterChain 是一个接口,这个接口只有一个实现类 DefaultSecurityFilterChain,构建 DefaultSecurityFilterChain 的第一个参数是拦截规则,也就是哪些路径需要拦截,第二个参数则是过滤器链,这里我给了一个空集合,也就是我们的 Spring Security 会拦截下所有的请求,然后在一个空集合中走一圈就结束了,相当于不拦截任何请求。,此时重启项目,你会发现 /hello 也是可以直接访问的,就是因为这个路径不经过任何过滤器。,其实我觉得目前这中新写法比以前老的写法更直观,更容易让大家理解到 Spring Security 底层的过滤器链工作机制。,有小伙伴会说,这写法跟我以前写的也不一样呀!这么配置,我也不知道 Spring Security 中有哪些过滤器,其实,换一个写法,我们就可以将这个配置成以前那种样子:,这么写,就跟以前的写法其实没啥大的差别了。,在最新版中,小伙伴们发现,很多常见的方法废弃了,如下图:,包括大家熟悉的用来连接各个配置项的 and() 方法现在也废弃了,并且按照官方的说法,将在 Spring Security7 中彻底移除该方法。,也就是说,你以后见不到类似下面这样的配置了:,and() 方法将被移除!,从上面 and 方法的注释中小伙伴们可以看到,官方现在是在推动基于 Lambda 的配置来代替传统的链式配置,所以以后我们的写法就得改成下面这样啦:,其实,这里的几个方法倒不是啥新方法,只不过有的小伙伴可能之前不太习惯用上面这几个方法进行配置,习惯于链式配置。可是往后,就得慢慢习惯上面这种按照 Lambda 的方式来配置了,配置的内容倒很好理解,我觉得没啥好解释的。,自定义 JSON 登录也和之前旧版不太一样了。,小伙伴们知道,Spring Security 中默认的登录接口数据格式是 key-value 的形式,如果我们想使用 JSON 格式来登录,那么就必须自定义过滤器或者自定义登录接口,下面松哥先来和小伙伴们展示一下这两种不同的登录形式。,Spring Security 默认处理登录数据的过滤器是 UsernamePasswordAuthenticationFilter,在这个过滤器中,系统会通过 request.getParameter(this.passwordParameter) 的方式将用户名和密码读取出来,很明显这就要求前端传递参数的形式是 key-value。,如果想要使用 JSON 格式的参数登录,那么就需要从这个地方做文章了,我们自定义的过滤器如下:,看过松哥之前的 Spring Security 系列文章的小伙伴,这段代码应该都是非常熟悉了。,最后,我们还需要对这个过滤器进行配置:,这里就是配置一个 JsonLoginFilter 的 Bean,并将之添加到 Spring Security 过滤器链中即可。,在 Spring Boot3 之前(Spring Security6 之前),上面这段代码就可以实现 JSON 登录了。,但是从 Spring Boot3 开始,这段代码有点瑕疵了,直接用已经无法实现 JSON 登录了,具体原因松哥下文分析。,另外一种自定义 JSON 登录的方式是直接自定义登录接口,如下:,这里直接自定义登录接口,请求参数通过 JSON 的形式来传递。拿到用户名密码之后,调用 AuthenticationManager#authenticate 方法进行认证即可。认证成功之后,将认证后的用户信息存入到 SecurityContextHolder 中。,最后再配一下登录接口就行了:,这也算是一种使用 JSON 格式参数的方案。在 Spring Boot3 之前(Spring Security6 之前),上面这个方案也是没有任何问题的。,从 Spring Boot3(Spring Security6) 开始,上面这两种方案都出现了一些瑕疵。,具体表现就是:当你调用登录接口登录成功之后,再去访问系统中的其他页面,又会跳转回登录页面,说明访问登录之外的其他接口时,系统不知道你已经登录过了。,产生上面问题的原因,主要在于 Spring Security 过滤器链中有一个过滤器发生变化了:,在 Spring Boot3 之前,Spring Security 过滤器链中有一个名为 SecurityContextPersistenceFilter 的过滤器,这个过滤器在 Spring Boot2.7.x 中废弃了,但是还在使用,在 Spring Boot3 中则被从 Spring Security 过滤器链中移除了,取而代之的是一个名为 SecurityContextHolderFilter 的过滤器。,在第一小节和小伙伴们介绍的两种 JSON 登录方案在 Spring Boot2.x 中可以运行在 Spring Boot3.x 中无法运行,就是因为这个过滤器的变化导致的。,所以接下来我们就来分析一下这两个过滤器到底有哪些区别。,先来看 SecurityContextPersistenceFilter 的核心逻辑:,我这里只贴出来了一些关键的核心代码:,这就是 Spring Security 认证的一个大致流程。,然而,到了 Spring Boot3 之后,这个过滤器被 SecurityContextHolderFilter 取代了,我们来看下 SecurityContextHolderFilter 过滤器的一个关键逻辑:,小伙伴们看到,前面的逻辑基本上还是一样的,不一样的是 finally 中的代码,finally 中少了一步向 HttpSession 保存 SecurityContext 的操作。,这下就明白了,用户登录成功之后,用户信息没有保存到 HttpSession,导致下一次请求到达的时候,无法从 HttpSession 中读取到 SecurityContext 存到 SecurityContextHolder 中,在后续的执行过程中,Spring Security 就会认为当前用户没有登录。,这就是问题的原因!,找到原因,那么问题就好解决了。,首先问题出在了过滤器上,直接改过滤器倒也不是不可以,但是,既然 Spring Security 在升级的过程中抛弃了之前旧的方案,我们又费劲的把之前旧的方案写回来,好像也不合理。,其实,Spring Security 提供了另外一个修改的入口,在 org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter#successfulAuthentication 方法中,源码如下:,这个方法是当前用户登录成功之后的回调方法,小伙伴们看到,在这个回调方法中,有一句 this.securityContextRepository.saveContext(context, request, response);,这就表示将当前登录成功的用户信息存入到 HttpSession 中。,在当前过滤器中,securityContextRepository 的类型是 RequestAttributeSecurityContextRepository,这个表示将 SecurityContext 存入到当前请求的属性中,那很明显,在当前请求结束之后,这个数据就没了。在 Spring Security 的自动化配置类中,将 securityContextRepository 属性指向了 DelegatingSecurityContextRepository,这是一个代理的存储器,代理的对象是 RequestAttributeSecurityContextRepository 和 HttpSessionSecurityContextRepository,所以在默认的情况下,用户登录成功之后,在这里就把登录用户数据存入到 HttpSessionSecurityContextRepository 中了。,当我们自定义了登录过滤器之后,就破坏了自动化配置里的方案了,这里使用的 securityContextRepository 对象就真的是 RequestAttributeSecurityContextRepository 了,所以就导致用户后续访问时系统以为用户未登录。,那么解决方案很简单,我们只需要为自定义的过滤器指定 securityContextRepository 属性的值就可以了,如下:,小伙伴们看到,最后调用 setSecurityContextRepository 方法设置一下就行。,那么对于自定义登录接口的问题,解决思路也是类似的:,小伙伴们看到,在登录成功之后,开发者自己手动将数据存入到 HttpSession 中,这样就能确保下个请求到达的时候,能够从 HttpSession 中读取到有效的数据存入到 SecurityContextHolder 中了。,好啦,Spring Boot 新旧版本交替中,一个小小的问题,希望小伙伴们能够有所收获。

文章版权声明

 1 原创文章作者:cmcc,如若转载,请注明出处: https://www.52hwl.com/28053.html

 2 温馨提示:软件侵权请联系469472785#qq.com(三天内删除相关链接)资源失效请留言反馈

 3 下载提示:如遇蓝奏云无法访问,请修改lanzous(把s修改成x)

 免责声明:本站为个人博客,所有软件信息均来自网络 修改版软件,加群广告提示为修改者自留,非本站信息,注意鉴别

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2023年6月23日
下一篇 2023年7月15日