此版本仍在开发中,尚不被认为是稳定的。对于最新的稳定版本,请使用 Spring Security 6.5.3! |
Kotlin 配置
Spring Security Kotlin 配置自 Spring Security 5.3 以来一直可用。 它允许用户使用本机 Kotlin DSL 配置 Spring Security。
Spring Security 提供了一个示例应用程序来演示 Spring Security Kotlin 配置的使用。 |
Http安全
Spring Security 如何知道我们要要求所有用户都经过身份验证?
Spring Security 如何知道我们想要支持基于表单的身份验证?
有一个配置类(称为SecurityFilterChain
)在幕后被调用。
它配置了以下默认实现:
import org.springframework.security.config.annotation.web.invoke
@Bean
open fun filterChain(http: HttpSecurity): SecurityFilterChain {
http {
authorizeHttpRequests {
authorize(anyRequest, authenticated)
}
formLogin { }
httpBasic { }
}
return http.build()
}
确保导入org.springframework.security.config.annotation.web.invoke 函数在您的类中启用 Kotlin DSL,因为 IDE 不会总是自动导入该方法,从而导致编译问题。 |
默认配置(如前面的示例所示):
-
确保对我们应用程序的任何请求都需要对用户进行身份验证
-
允许用户使用基于表单的登录进行身份验证
-
允许用户使用 HTTP 基本身份验证进行身份验证
请注意,此配置与 XML 命名空间配置平行:
<http>
<intercept-url pattern="/**" access="authenticated"/>
<form-login />
<http-basic />
</http>
多个 HttpSecurity 实例
为了在某些区域需要不同保护的应用程序中有效管理安全性,我们可以在securityMatcher
DSL 方法。
这种方法使我们能够定义针对应用程序特定部分量身定制的不同安全配置,从而增强整体应用程序的安全性和控制力。
我们可以配置多个HttpSecurity
实例,就像我们可以有多个<http>
块。
关键是要注册多个SecurityFilterChain
@Bean
s.
以下示例对以/api/
:
import org.springframework.security.config.annotation.web.invoke
@Configuration
@EnableWebSecurity
class MultiHttpSecurityConfig {
@Bean (1)
open fun userDetailsService(): UserDetailsService {
val users = User.withDefaultPasswordEncoder()
val manager = InMemoryUserDetailsManager()
manager.createUser(users.username("user").password("password").roles("USER").build())
manager.createUser(users.username("admin").password("password").roles("USER","ADMIN").build())
return manager
}
@Bean
@Order(1) (2)
open fun apiFilterChain(http: HttpSecurity): SecurityFilterChain {
http {
securityMatcher("/api/**") (3)
authorizeHttpRequests {
authorize(anyRequest, hasRole("ADMIN"))
}
httpBasic { }
}
return http.build()
}
@Bean (4)
open fun formLoginFilterChain(http: HttpSecurity): SecurityFilterChain {
http {
authorizeHttpRequests {
authorize(anyRequest, authenticated)
}
formLogin { }
}
return http.build()
}
}
1 | 像往常一样配置身份验证。 |
2 | 创建SecurityFilterChain 包含@Order 指定哪个SecurityFilterChain 应该首先考虑。 |
3 | 这http.securityMatcher() 声明该HttpSecurity 仅适用于以/api/ . |
4 | 创建另一个SecurityFilterChain .
如果 URL 不是以/api/ ,则使用此配置。
此配置在apiFilterChain ,因为它有一个@Order 值1 (否@Order 默认为 last)。 |
选择securityMatcher
或requestMatchers
一个常见的问题是:
有什么区别
http.securityMatcher()
method 和requestMatchers()
用于请求授权(即在http.authorizeHttpRequests()
)?
要回答这个问题,了解每个HttpSecurity
实例用于构建SecurityFilterChain
包含一个RequestMatcher
以匹配传入的请求。
如果请求与SecurityFilterChain
具有更高优先级(例如@Order(1)
),可以针对优先级较低的过滤器链(例如 no@Order
).
多个过滤器链的匹配逻辑由 |
默认值RequestMatcher
匹配任何请求,以确保 Spring Security 默认保护所有请求。
指定 |
如果没有过滤器链与特定请求匹配,则该请求不受 Spring Security 保护。 |
以下示例演示了一个仅保护以/secured/
:
import org.springframework.security.config.annotation.web.invoke
@Configuration
@EnableWebSecurity
class PartialSecurityConfig {
@Bean
open fun userDetailsService(): UserDetailsService {
// ...
}
@Bean
open fun securedFilterChain(http: HttpSecurity): SecurityFilterChain {
http {
securityMatcher("/secured/**") (1)
authorizeHttpRequests {
authorize("/secured/user", hasRole("USER")) (2)
authorize("/secured/admin", hasRole("ADMIN")) (3)
authorize(anyRequest, authenticated) (4)
}
httpBasic { }
formLogin { }
}
return http.build()
}
}
1 | 以/secured/ 将受到保护,但任何其他请求都不受保护。 |
2 | 请求/secured/user 需要ROLE_USER 柄。 |
3 | 请求/secured/admin 需要ROLE_ADMIN 柄。 |
4 | 任何其他请求(例如/secured/other ) 只需要经过身份验证的用户。 |
建议提供 |
请注意,requestMatchers
方法仅适用于单个授权规则。
其中列出的每个请求也必须与总体请求匹配securityMatcher
对于这个特定的HttpSecurity
实例,用于创建SecurityFilterChain
.
用anyRequest()
在此示例中,匹配此特定请求中的所有其他请求SecurityFilterChain
(必须以/secured/
).
有关更多信息,请参阅授权 HttpServletRequests |
SecurityFilterChain
端点
中的多个过滤器SecurityFilterChain
直接提供端点,例如UsernamePasswordAuthenticationFilter
由http.formLogin()
并提供POST /login
端点。
在上面的示例中,/login
endpoint 不匹配http.securityMatcher("/secured/**")
因此,该应用程序不会有任何GET /login
或POST /login
端点。
此类请求将返回404 Not Found
.
这常常让用户感到惊讶。
指定http.securityMatcher()
影响与该请求匹配的请求SecurityFilterChain
.
但是,它不会自动影响过滤器链提供的端点。
在这种情况下,您可能需要自定义希望筛选器链提供的任何终结点的 URL。
以下示例演示了一种配置,用于保护以/secured/
并拒绝所有其他请求,同时还自定义SecurityFilterChain
:
import org.springframework.security.config.annotation.web.invoke
@Configuration
@EnableWebSecurity
class SecuredSecurityConfig {
@Bean
open fun userDetailsService(): UserDetailsService {
// ...
}
@Bean
@Order(1)
open fun securedFilterChain(http: HttpSecurity): SecurityFilterChain {
http {
securityMatcher("/secured/**") (1)
authorizeHttpRequests {
authorize(anyRequest, authenticated) (2)
}
formLogin { (3)
loginPage = "/secured/login"
loginProcessingUrl = "/secured/login"
permitAll = true
}
logout { (4)
logoutUrl = "/secured/logout"
logoutSuccessUrl = "/secured/login?logout"
permitAll = true
}
}
return http.build()
}
@Bean
open fun defaultFilterChain(http: HttpSecurity): SecurityFilterChain {
http {
authorizeHttpRequests {
authorize(anyRequest, denyAll) (5)
}
}
return http.build()
}
}
1 | 以/secured/ 将受到此过滤链的保护。 |
2 | 以/secured/ 需要经过身份验证的用户。 |
3 | 自定义表单登录以在 URL 前加上/secured/ . |
4 | 自定义注销以在 URL 前加上/secured/ . |
5 | 所有其他请求都将被拒绝。 |
此示例自定义登录和注销页面,这禁用了 Spring Security 生成的页面。
必须为 |
真实世界示例
以下示例演示了将所有这些元素放在一起的更真实的配置:
import org.springframework.security.config.annotation.web.invoke
@Configuration
@EnableWebSecurity
class BankingSecurityConfig {
@Bean (1)
open fun userDetailsService(): UserDetailsService {
val users = User.withDefaultPasswordEncoder()
val manager = InMemoryUserDetailsManager()
manager.createUser(users.username("user1").password("password").roles("USER", "VIEW_BALANCE").build())
manager.createUser(users.username("user2").password("password").roles("USER").build())
manager.createUser(users.username("admin").password("password").roles("ADMIN").build())
return manager
}
@Bean
@Order(1) (2)
open fun approvalsSecurityFilterChain(http: HttpSecurity): SecurityFilterChain {
val approvalsPaths = arrayOf("/accounts/approvals/**", "/loans/approvals/**", "/credit-cards/approvals/**")
http {
securityMatcher(*approvalsPaths)
authorizeHttpRequests {
authorize(anyRequest, hasRole("ADMIN"))
}
httpBasic { }
}
return http.build()
}
@Bean
@Order(2) (3)
open fun bankingSecurityFilterChain(http: HttpSecurity): SecurityFilterChain {
val bankingPaths = arrayOf("/accounts/**", "/loans/**", "/credit-cards/**", "/balances/**")
val viewBalancePaths = arrayOf("/balances/**")
http {
securityMatcher(*bankingPaths)
authorizeHttpRequests {
authorize(viewBalancePaths, hasRole("VIEW_BALANCE"))
authorize(anyRequest, hasRole("USER"))
}
}
return http.build()
}
@Bean (4)
open fun defaultSecurityFilterChain(http: HttpSecurity): SecurityFilterChain {
val allowedPaths = arrayOf("/", "/user-login", "/user-logout", "/notices", "/contact", "/register")
http {
authorizeHttpRequests {
authorize(allowedPaths, permitAll)
authorize(anyRequest, authenticated)
}
formLogin {
loginPage = "/user-login"
loginProcessingUrl = "/user-login"
}
logout {
logoutUrl = "/user-logout"
logoutSuccessUrl = "/?logout"
}
}
return http.build()
}
}
1 | 首先配置身份验证设置。 |
2 | 定义一个SecurityFilterChain 实例与@Order(1) ,这意味着此过滤器链将具有最高优先级。
此筛选器链仅适用于以/accounts/approvals/ ,/loans/approvals/ 或/credit-cards/approvals/ .
对此过滤器链的请求需要ROLE_ADMIN 权限并允许 HTTP 基本身份验证。 |
3 | 接下来,创建另一个SecurityFilterChain 实例与@Order(2) 这将被视为第二位。
此筛选器链仅适用于以/accounts/ ,/loans/ ,/credit-cards/ 或/balances/ .
请注意,由于此筛选器链是第二个,因此包含/approvals/ 将匹配上一个过滤器链,并且不会被此过滤器链匹配。
对此过滤器链的请求需要ROLE_USER 柄。
此筛选器链不定义任何身份验证,因为下一个(默认)筛选器链包含该配置。 |
4 | 最后,创建一个额外的SecurityFilterChain 实例,而不使用@Order 注解。
此配置将处理其他过滤器链未涵盖的请求,并将最后处理(no@Order 默认为 last)。
匹配的请求 ,/ /user-login ,/user-logout ,/notices ,/contact 和/register 允许无需身份验证即可访问。
任何其他请求都需要对用户进行身份验证才能访问其他过滤器链未显式允许或保护的任何 URL。 |
模块化 HttpSecurityDsl 配置
许多用户更喜欢他们的 Spring Security 配置位于集中位置,并会选择在单个SecurityFilterChain
实例。
但是,有时用户可能希望将配置模块化。
这可以使用以下方法完成:
自 Spring Security Kotlin Dsl (HttpSecurityDsl )用途HttpSecurity ,所有 Java Modular Bean 自定义都应用在模块化 HttpSecurity 配置之前。 |
HttpSecurityDsl 的 HttpSecurityDsl 中。() → 单位豆
如果您想模块化安全配置,可以将逻辑放在HttpSecurityDsl.() → Unit
豆。
例如,以下配置将确保所有HttpSecurityDsl
实例配置为:
@Bean
fun httpSecurityDslBean(): HttpSecurityDsl.() -> Unit {
return {
headers {
contentSecurityPolicy {
(1)
policyDirectives = "object-src 'none'"
}
}
(2)
redirectToHttps { }
}
}
1 | 将内容安全策略设置为object-src 'none' |
2 | 将任何请求重定向到 https |
顶级安全 Dsl Bean
如果您希望进一步模块化安全配置,Spring Security 将自动应用任何顶级安全 Dsl Bean。
顶级安全 Dsl 可以汇总为匹配的任何类 Dsl 类public HttpSecurityDsl.*(<Dsl>)
.
这转换为任何安全 Dsl,它是HttpSecurityDsl
.
几个例子可以帮助澄清。 如果ContentTypeOptionsDsl.() → Unit
作为 Bean 发布,它不会自动应用,因为它是HeadersDsl#contentTypeOptions(ContentTypeOptionsDsl.() → Unit)
并且不是定义在HttpSecurityDsl
. 但是,如果HeadersDsl.() → Unit
作为 Bean 发布,它将自动应用,因为它是HttpSecurityDsl.headers(HeadersDsl.() → Unit)
.
例如,以下配置确保所有HttpSecurityDsl
实例配置为:
@Bean
fun headersSecurity(): HeadersDsl.() -> Unit {
return {
contentSecurityPolicy {
(1)
policyDirectives = "object-src 'none'"
}
}
}
1 | 将内容安全策略设置为object-src 'none' |
Dsl Bean 排序
首先,所有模块化 HttpSecurity 配置都应用了,因为 Kotlin Dsl 使用HttpSecurity
豆。
其次,每个 HttpSecurityDsl.() → Unit Bean 都使用 ObjectProvider#orderedStream() 应用。
这意味着,如果有多个HttpSecurity.() → Unit
Bean,可以将@Order注释添加到 Bean 定义中以控制排序。
接下来,查找每个顶级安全 Dsl Bean 类型,并使用ObjectProvider#orderedStream()
.
如果存在不同类型的顶级安全 Bean(例如HeadersDsl.() → Unit
和HttpsRedirectDsl.() → Unit
),则调用每个 Dsl 类型的顺序是未定义的。
但是,具有相同顶级安全 Bean 类型的每个实例的顺序由ObjectProvider#orderedStream()
并且可以使用@Order
在 Bean 上定义。
最后,HttpSecurityDsl
Bean 作为 Bean 注入。
都*Dsl.() → Unit
Bean 在HttpSecurityDsl
Bean 被创建。
这允许覆盖*Dsl.() → Unit
豆。
您可以在下面找到一个示例来说明排序:
// All of the Java Modular Configuration is applied first (1)
@Bean (5)
fun springSecurity(http: HttpSecurity): SecurityFilterChain {
http {
authorizeHttpRequests {
authorize(anyRequest, authenticated)
}
}
return http.build()
}
@Bean
@Order(Ordered.LOWEST_PRECEDENCE) (3)
fun userAuthorization(): HttpSecurityDsl.() -> Unit {
return {
authorizeHttpRequests {
authorize("/users/**", hasRole("USER"))
}
}
}
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE) (2)
fun adminAuthorization(): HttpSecurityDsl.() -> Unit {
return {
authorizeHttpRequests {
authorize("/admins/**", hasRole("ADMIN"))
}
}
}
(4)
@Bean
fun contentSecurityPolicy(): HeadersDsl.() -> Unit {
return {
contentSecurityPolicy {
policyDirectives = "object-src 'none'"
}
}
}
@Bean
fun contentTypeOptions(): HeadersDsl.() -> Unit {
return {
contentTypeOptions { }
}
}
@Bean
fun httpsRedirect(): HttpsRedirectDsl.() -> Unit {
return { }
}
1 | 所有模块化 HttpSecurity 配置都应用,因为 Kotlin Dsl 使用HttpSecurity 豆。 |
2 | 都HttpSecurity.() → Unit 实例。
这adminAuthorization 豆子的@Order 所以首先应用它。
如果没有@Order 注释HttpSecurity.() → Unit Beans 或@Order 注释具有相同的值,则HttpSecurity.() → Unit 实例被应用为未定义。 |
3 | 这userAuthorization 由于是HttpSecurity.() → Unit |
4 | 命令*Dsl.() → Unit 类型未定义。
在此示例中,的顺序contentSecurityPolicy ,contentTypeOptions 和httpsRedirect 是未定义的。
如果@Order(Ordered.HIGHEST_PRECEDENCE) 被添加到contentTypeOptions ,那么我们就会知道contentTypeOptions 在contentSecurityPolicy (它们是同一类型),但我们不知道是否httpsRedirect 在HeadersDsl.() → Unit 豆。 |
5 | 毕竟*Dsl.() → Unit 应用 bean,则HttpSecurityDsl 作为 Bean 传入。 |