Kotlin 配置
Spring Security Kotlin 配置从 Spring Security 5.3 开始可用。它允许用户使用原生的 Kotlin DSL 来配置 Spring Security。
Spring Security 提供了 一个示例应用程序,以演示 Spring Security Kotlin 配置的使用。
HttpSecurity
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 Basic认证进行身份验证
请注意,此配置与XML命名空间配置相对应:
<http>
<intercept-url pattern="/**" access="authenticated"/>
<form-login />
<http-basic />
</http>
多个 HttpSecurity 实例
要有效地管理应用程序的安全性,其中某些区域需要不同的保护,我们可以使用多个过滤器链以及 securityMatcher DSL 方法。这种方法允许我们为应用程序的特定部分定义不同的安全配置,从而增强整个应用程序的安全性和控制。
我们可以配置多个 HttpSecurity 实例,就像我们可以在 XML 中拥有多个 <http> 块一样。关键是要注册多个 SecurityFilterChain @Bean。下面的示例对以 /api/ 开头的 URL 有不同的配置:
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()
}
}
如常配置身份验证。
创建一个包含
@Order的SecurityFilterChain实例,以指定应首先考虑哪个SecurityFilterChain。http.securityMatcher()声明此HttpSecurity仅适用于以/api/开头的 URL。创建另一个
SecurityFilterChain实例。如果 URL 不是以/api/开头,则使用此配置。由于它的@Order值在1之后(没有@Order默认为最后),因此该配置会在apiFilterChain之后被考虑。
选择 securityMatcher 或 requestMatchers
一个常见的问题是:
http.securityMatcher()方法和用于请求授权的requestMatchers()(即在http.authorizeHttpRequests()内)之间有什么区别?
要回答这个问题,有助于理解每个用于构建 SecurityFilterChain 的 HttpSecurity 实例都包含一个 RequestMatcher 来匹配传入的请求。如果请求与更高优先级的 SecurityFilterChain(例如 @Order(1))不匹配,则可以尝试将该请求与较低优先级的过滤器链(例如没有 @Order)进行匹配。
匹配多个过滤器链的逻辑由FilterChainProxy执行。
默认的 RequestMatcher 匹配 任何请求 以确保 Spring Security 默认保护 所有请求。
指定 securityMatcher 会覆盖此默认设置。
如果没有任何过滤器链与某个请求匹配,则该请求不受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()
}
}
以
/secured/开头的请求将受到保护,但其他任何请求都不受保护。对
/secured/user的请求需要ROLE_USER权限。对
/secured/admin的请求需要ROLE_ADMIN权限。其他任何请求(例如
/secured/other)只需要一个已认证的用户。
建议提供一个不指定任何 securityMatcher 的 SecurityFilterChain,以确保整个应用程序都受到保护,如前面的示例所示。
请注意,requestMatchers 方法仅适用于单个授权规则。列出的每个请求还必须与此特定 HttpSecurity 实例的整体 securityMatcher 匹配,该实例用于创建 SecurityFilterChain。在此示例中使用 anyRequest() 会匹配此特定 SecurityFilterChain 中的所有其他请求(这些请求必须以 /secured/ 开头)。
有关 requestMatchers 的更多信息,请参见 授权 HttpServletRequests。
SecurityFilterChain 端点
SecurityFilterChain 中的几个过滤器直接提供了端点,例如通过 http.formLogin() 设置的 UsernamePasswordAuthenticationFilter 提供了 POST /login 端点。在上面的例子中,/login 端点不被 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()
}
}
以
/secured/开头的请求将受此过滤器链保护。以
/secured/开头的请求需要经过身份验证的用户。自定义表单登录,使 URL 以前缀
/secured/开头。自定义注销,使 URL 以前缀
/secured/开头。所有其他请求将被拒绝。
此示例自定义了登录和注销页面,这将禁用 Spring Security 生成的页面。你必须自行提供 GET /secured/login 和 GET /secured/logout 的自定义端点。请注意,Spring Security 仍然为你提供了 POST /secured/login 和 POST /secured/logout 端点。
真实世界示例
以下示例演示了一个稍微更接近实际的配置,将所有这些元素组合在一起:
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()
}
}
首先配置认证设置。
定义一个带有
@Order(1)的SecurityFilterChain实例,这意味着该过滤链将具有最高优先级。此过滤链仅适用于以/accounts/approvals/、/loans/approvals/或/credit-cards/approvals/开头的请求。对该过滤链的请求需要ROLE_ADMIN权限,并允许 HTTP Basic 认证。接下来,创建另一个带有
@Order(2)的SecurityFilterChain实例,这将是第二个被考虑的过滤链。此过滤链仅适用于以/accounts/、/loans/、/credit-cards/或/balances/开头的请求。请注意,由于此过滤链是第二个,任何包含/approvals/的请求将匹配前一个过滤链,而不会被此过滤链匹配。对该过滤链的请求需要ROLE_USER权限。此过滤链没有定义任何认证,因为下一个(默认)过滤链包含了该配置。最后,创建一个没有
@Order注解的SecurityFilterChain实例。此配置将处理其他过滤链未覆盖的请求,并将在最后处理(没有@Order默认为最后)。匹配/、/user-login、/user-logout、/notices、/contact和/register的请求允许在不进行认证的情况下访问。任何其他请求都需要用户进行认证才能访问未明确允许或受其他过滤链保护的任何 URL。