新版本Spring Security中使用Lambda DSL替换旧版本配置
至暗时刻
之前的项目不是用上了最新最热的 SpringBoot 3.0 嘛,今天我脑子一热,看了一眼官网版本,直接把 SpringBoot 的版本从3.0.6
升级到了3.1.1
。
重新构建 Gradle 之后启动项目,SpringBoot 没出问题,但启动项目时,依赖传递更新的新版 Spring Security 中有好几个方法移除并标记为弃用(大红色的警告,看上去真的很吓人),虽然代码还能够正常运行,但是本着弃用方法必然有替用方法的原理,故参考文档后写下该文。
原本项目中的 Security 配置代码如下(仅包含了弃用字段的配置),主要集中在SecurityFilterChain
的配置 Bean 上:
1 |
|
拨云见日
如原来的配置文件所示,这四个方法HttpSecurity.csrf()
,HttpSecurity.sessionManagement()
,HttpSecurity.exceptionHandling()
,HttpSecurity.cors()
都被标记为弃用了。而根据官网的信息,日后更新的 Spring Security 7 中会完全移除这些方法,Spring Security 6 中虽然两种方法都可以使用,但会产生方法已弃用的警告。
The Lambda DSL is present in Spring Security since version 5.2, and it allows HTTP security to be configured using lambdas.
You may have seen this style of configuration in the Spring Security documentation or samples. Let us take a look at how a lambda configuration of HTTP security compares to the previous configuration style.
新版本的Spring Security 6
中,官方推荐使用 Lambda DSL 配置方法来配置 Spring Security。相比过去的配置方式,可以抛弃大量的.and()
连接方法,同时 Lambda DSL 更符合现代的编程思想,能让配置方式更加简洁、清晰。
实际上从Spring Security 5.2
中,部分方法就已经使用了 Lambda DSL。我在上个版本也已经使用HttpSecurity.authorizeHttpRequests()
方法的 Lambda DSL 配置方式。该方法是最早实现且推荐使用 Lambda DSL 配置的方法之一。这次的更新将 DSL 的应用范围变得更大了,囊括了几乎所有常用的HttpSecurity
配置。
1 |
|
Lambda 方式的源码分析
新版本的 Spring Security 中,HttpSecurity
的配置方法都是通过 Lambda 表达式实现的,要理解 Lambda 配置的原理,首先要从HttpSecurity
开始。
HttpSecurity:核心配置类
HttpSecurity
是 Spring Security 中最核心的配置类,它的详细继承关系如下,可以看到,HttpSecurity
继承自 AbstractConfiguredSecurityBuilder
,同时实现了SecurityBuilder
和HttpSecurityBuilder
两个接口。
HttpSecurity 的定义如下:
1 | public final class HttpSecurity |
该类最重要的职责是进行各种类型的安全配置,通过匿名函数传入各种各样的继承于AbstractHttpConfigurer
的配置器,从这些 Configurer 中获取配置信息对HttpSecurity
进行配置。
配置示例
这里以设置不通过 Session 获取 SecurityContext 的方法:HttpSecurity.sessionManagement()
方法为例,在HttpSecurity
中存在大量类似的方法,新版本中,这些方法都是通过 Lambda 表达式实现的,使用 Lambda 表达式接受配置类,对该配置类进行链式调用,从而实现自定义配置。
1 | public HttpSecurity sessionManagement(Customizer<SessionManagementConfigurer<HttpSecurity>> sessionManagementCustomizer) throws Exception { |
其主要的处理流程如下:
调用
Customizer
接口的customize()
方法,对 Configurer 对象进行配置通过
getOrApply()
方法获取或者应用一个 Configurer 对象至HttpSecurity
中返回该
HttpSecurity
对象,以便进行链式调用配置
其中几个核心步骤的源码分析如下:
customize():接受配置类
HttpSecurity
中的大量方法都会接受一个类似于Customizer<xxxConfigurer<HttpSecurity>>
类型的参数,Customizer
是一个函数式接口,通过声明@FunctionalInterface
注解来标注。
1 |
|
该 class 中只有一个customize(T t)
方法,该方法接受一个泛型参数,该方法的作用是对泛型参数进行配置。在 Spring Security 中用于支持 Lambda DSL 的匿名函数配置方法。
可以注意的是该接口提供了一个
withDefaults()
方法,该方法返回一个包含空方法的对象,这样做的目的是实现 SpringBoot“约定优于配置”的编程思想,如果不需要配置HttpSecurity
,则可以直接使用withDefaults()
方法,如下例所示:
1 http.sessionManagement(Customizer.withDefaults());该方法返回的
Customizer
对象的customize()
方法是一个空方法,不会对HttpSecurity
进行任何配置。
getOrApply():获取或应用配置器
该方法定义在HttpSecurity
内部,属于该类的一个私有方法,该方法的作用是获取或者应用一个SecurityConfigurerAdapter
对象。
1 | private <C extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity>> C getOrApply(C configurer) |
其中核心的getConfigurer()
定义在其父类AbstractConfiguredSecurityBuilder
中,该方法的作用是获取SecurityConfigurerAdapter
对象,并检测该配置类是否已经在 Spring Security 中配置过,如果未被配置,则返回null
,使得该方法调用apply()
方法。
1 | public <C extends SecurityConfigurerAdapter<O, B>> C apply(C configurer) throws Exception { |
apply()
方法的作用也很明显,当该SecurityConfigurerAdapter
未被配置时,将该对象添加到 Spring Security 中,并返回该对象。这样的设计使得 Spring Security 的配置类可以被多次配置,而不会产生冲突。
Lambda DSL 的优势
General From ChatGPT
在 Java 中使用 Lambda 表达式的优势有以下几点:
简洁性:使用 Lambda 表达式可以大大减少代码的冗余,提高代码的可读性和可维护性。相比传统的匿名内部类,Lambda 表达式可以更简洁地表达函数式接口的实现。
函数式编程支持:Lambda 表达式是 Java 对函数式编程的一种支持,可以方便地处理函数作为参数传递、函数作为返回值等函数式编程的特性。通过 Lambda 表达式,可以更轻松地编写函数式风格的代码。
代码灵活性:使用 Lambda 表达式可以将行为作为参数传递给方法,从而使得代码更加灵活。可以根据需要在运行时传递不同的行为,而不需要编写多个具体的实现类或匿名内部类。
并行处理:Lambda 表达式可以与 Java 8 引入的 Stream API 结合使用,使得并行处理数据变得更加容易。通过使用 Lambda 表达式和 Stream API,可以更方便地编写并行化的代码,提高程序的性能。
内部迭代:传统的迭代方式需要显式地编写循环结构,而 Lambda 表达式可以隐式地进行迭代。通过使用 Lambda 表达式,可以更加简洁地处理集合元素的迭代操作。
写在最后
其实对于个人而言,Lambda DSL 这样的方式确实能够提供更好的可读性
但是对于团队而言,这样的方式可能会导致团队成员之间的代码风格不统一,导致代码可读性下降,所以在团队中使用 Lambda DSL 的方式需要谨慎。
人的惰性是无穷的,很多时候我们都会选择最简单的方式来完成工作,而不是最好的方式。