领先一步
VMware 提供培训和认证,以加速您的进步。
了解更多嗨,Spring 粉丝们!欢迎收看另一期《Spring 提示》。在本期节目中,我们将介绍 Spring Security 的新 Kotlin DSL。我非常喜欢 Kotlin。我在其他几个 Spring 提示视频中介绍过 Kotlin:Kotlin 编程语言、Bootiful Kotlin Redux 和 Spring 对协程的支持。其中一些视频非常旧!Spring 社区中已经有许多不同的项目正在发布 Kotlin DSL。其中包括 Spring Framework、Spring Webflux、Spring Data、Spring Cloud Contract 和 Spring Cloud Gateway。现在,还有 Spring Security!
Spring Security 是一个很棒的项目——它解决了业界一些最棘手的问题,并帮助人们保护他们的应用程序。而且,如果这还不够,它还表现出坚定决心让安全变得简单。如果您曾经在早期使用过 Spring Security,您就会知道它需要大量的 XML——页面!——才能完成任何操作。这种情况得到了改善,在 Spring Security 3 中,您可以使用一两个 XML 节来锁定应用程序,并使用通用的默认值。然后,在 Spring Security 4 中,他们引入了 Java DSL,使人们能够利用其编译器的功能来帮助他们验证事物。随着时间的推移,Spring Security 还引入了通用的默认值。现在,您可以注册一个 UserDetailsService
,Spring Security 将锁定基于 Servlet 的 HTTP 应用程序中的所有 HTTP 端点,并需要身份验证。再简单不过了!或者说,真的如此吗?在 Spring Security 5.2 中,他们对 Spring Security 进行了一些备受赞赏的改进。现在,除了使用过去的流畅 Java 配置 DSL 之外,还有一种新方法,您可以在其中提供一个 lambda 并获得一个上下文对象,然后您可以使用该对象。您不再需要缩进您的 Spring Security API 以便理解其意图!现在,在本期节目中,我们将通过快速了解全新的 Spring Security Kotlin DSL 将事情提升到一个新的水平。
请记住,Spring Security 解决了两个正交问题:身份验证和授权。身份验证回答了“谁正在发出请求?”这个问题。是 Josh 还是 Jane?授权回答了“请求者在系统内部拥有什么权限?”这个问题。身份验证完全是关于插入身份提供者。有无数种方法可以做到这一点(Active Directory、内存中的用户名和密码、LDAP、SAML 等)。它更多地是关于插入实现。我们只使用内存中的用户名和密码身份验证管理器,因为我们需要它,而且这并不是 DSL 真正擅长的地方。
DSL 最有用之处不在于交换给定类型的实现,而在于描述规则或自定义行为。因此,我们将使用 Spring Security DSL 来自定义授权行为。
以下是使用 Spring Security 的基于 Spring Boot 的应用程序。我从 Spring Initializr 生成一个使用 Kotlin 的新项目,并使用 Spring Security 和 Spring Boot 2.3.M2 或更高版本。它使用函数式 bean 注册 DSL 以编程方式注册 bean。我们讨论过编程方式的 bean 注册在 2017 年很久以前的《Spring 提示》视频中。当然,该视频演示了它在 Java 中的使用,但该应用程序在 Kotlin 中基本上是相同的:您通过将 bean 包装在对 bean
函数的调用中来注册 bean。
第一个 bean 是 InMemoryUserDetailsManager
。第二个 bean 是一个函数式 HTTP 端点 /greetings
。当 HTTP 请求进来时,我们从当前请求中提取经过身份验证的主体,提取名称,然后构建一个 ServerResponse
,其主体将由 Map<String,String>
表示。
有趣的部分是 KotlinSecurityConfiguration
类。它扩展了 WebSecurityConfigurerAdapter
。我们可能会在那里覆盖一些方法,但我选择覆盖 configure
方法来指定两件事:我想选择加入 HTTP 基本身份验证,并指定哪些路由需要身份验证,哪些路由是开放的。下面的配置规定所有对 /greetings/**
(/greetings/
端点及其下面的任何内容,如 /greetings/foo
)的请求都必须经过身份验证。第二个规则表示其他所有内容都是开放的。更具体的规则——/greetings/**
——在更开放的规则之前出现非常重要。这些规则按顺序从上到下进行评估。如果我们将第二个规则放在第一位,那么它将匹配每个请求,我们永远不需要评估 /greetings
的规则——它将有效地保持开放状态!
package com.example.kotlinsecurity
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication
import org.springframework.context.annotation.Bean
import org.springframework.context.support.beans
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
import org.springframework.security.config.web.servlet.invoke
import org.springframework.security.core.userdetails.User
import org.springframework.security.provisioning.InMemoryUserDetailsManager
import org.springframework.web.servlet.function.ServerResponse
import org.springframework.web.servlet.function.router
@SpringBootApplication
class KotlinSecurityApplication
@EnableWebSecurity
class KotlinSecurityConfiguration : WebSecurityConfigurerAdapter() {
override fun configure(http: HttpSecurity?) {
http {
httpBasic {}
authorizeRequests {
authorize("/greetings/**", hasAuthority("ROLE_ADMIN"))
authorize("/**", permitAll)
}
}
}
}
fun main(args: Array<String>) {
runApplication<KotlinSecurityApplication>(*args) {
addInitializers(beans {
bean {
fun user(user: String, pw: String, vararg roles: String) =
User.withDefaultPasswordEncoder().username(user).password(pw).roles(*roles).build()
InMemoryUserDetailsManager(user("jlong", "pw", "USER"), user("rwinch", "pw1", "USER", "ADMIN"))
}
bean {
router {
GET("/greetings") { request ->
request.principal().map { it.name }.map { ServerResponse.ok().body(mapOf("greeting" to "Hello, $it")) }.orElseGet { ServerResponse.badRequest().build() }
}
}
}
})
}
}
在本期节目中,我们介绍了 Spring Security 的新 Kotlin DSL。文本内容比代码多,因为,这很深刻,Spring Security 为您做了很多事情,因此 API 的表面积实际上是您想要做的自定义的最低限度,超出了已经合理的默认值。希望您学到了一些新东西,并会尝试一下 Spring Security DSL。