此版本仍在开发中,尚未被视为稳定版本。如需最新稳定版本,请使用 Spring Security 7.0.4spring-doc.cadn.net.cn

Kotlin 配置

Spring Security Kotlin配置自Spring Security 5.3版本起已经可用。 它允许用户通过使用原生的Kotlin DSL来配置Spring Security。spring-doc.cadn.net.cn

Spring Security 提供了一个示例应用,用于演示如何使用 Spring Security Kotlin 配置。spring-doc.cadn.net.cn

HttpSecurity

Spring Security 如何知道我们需要对所有用户进行身份验证? Spring Security 如何知道我们希望支持表单基于的身份验证? 背后会调用一个配置类(称为 SecurityFilterChain)。 它默认配置为如下实现:spring-doc.cadn.net.cn

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不会总是自动导入该方法,可能会导致编译问题。

默认配置(如上例所示):spring-doc.cadn.net.cn

注意,此配置与XML命名空间配置相对应:spring-doc.cadn.net.cn

<http>
	<intercept-url pattern="/**" access="authenticated"/>
	<form-login />
	<http-basic />
</http>

多个 HttpSecurity 实例

要有效地管理应用程序中的安全问题,某些区域需要不同的保护级别时,我们可以在 securityMatcher DSL 方法之外使用多个过滤器链。 这种方法允许我们为应用程序的特定部分定义独特的安全配置,从而增强整体应用的安全性和控制能力。spring-doc.cadn.net.cn

我们可以像在XML中可以有多个HttpSecurity块一样,配置多个<http>实例。 关键在于注册多个SecurityFilterChain @Bean。 以下示例对以/api/开头的URL有不同的配置:spring-doc.cadn.net.cn

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/ 开头的 URL。
4 创建另一个SecurityFilterChain的实例。 如果URL不以/api/开头,将使用此配置。 此配置会在apiFilterChain之后被考虑,因为它具有比@Order大的1值(如果没有@Order则默认为最后)。

选择securityMatcher or requestMatchers

常见问题有:spring-doc.cadn.net.cn

http.securityMatcher() 方法和 requestMatchers() 在请求授权(即在 http.authorizeHttpRequests() 中)方面有什么区别?spring-doc.cadn.net.cn

要回答这个问题,有助于理解每个用于构建HttpSecuritySecurityFilterChain实例包含一个RequestMatcher来匹配传入请求。 如果一个请求没有匹配具有更高优先级的SecurityFilterChain(例如@Order(1)),该请求可以尝试匹配较低优先级的过滤链(例如无@Order)。spring-doc.cadn.net.cn

多个过滤器链的匹配逻辑由 FilterChainProxy 执行。spring-doc.cadn.net.cn

The default RequestMatcher 匹配 所有请求 以确保 Spring Security 默认保护 所有请求spring-doc.cadn.net.cn

指定一个securityMatcher会覆盖这个默认设置。spring-doc.cadn.net.cn

若没有过滤器链匹配某个请求,则该请求将不受Spring Security保护spring-doc.cadn.net.cn

以下示例演示了一个单一的过滤器链,仅保护以/secured/开头的请求:spring-doc.cadn.net.cn

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)只需一个已认证的用户。

建议提供一个未指定任何securityMatcherSecurityFilterChain,以确保整个应用程序受到保护,如前面的示例中所示。spring-doc.cadn.net.cn

注意,requestMatchers 方法仅适用于个别授权规则。 每个在此处列出的请求还必须匹配此特定 securityMatcher 实例用于创建 HttpSecurity 的整体 SecurityFilterChain。 在这个例子中使用 anyRequest() 匹配的是该特定 SecurityFilterChain 中的所有其他请求(这些请求必须以 /secured/ 开头)。spring-doc.cadn.net.cn

授权 HttpServletRequest 请求 以获取更多关于requestMatchers的信息。spring-doc.cadn.net.cn

SecurityFilterChain端点

SecurityFilterChain 中的多个过滤器直接提供端点,例如由 http.formLogin() 设置并暴露 POST /login 端点的 UsernamePasswordAuthenticationFilter。 在 上述示例 中,/login 端点未被 http.securityMatcher("/secured/**") 匹配,因此该应用程序不会具有任何 GET /loginPOST /login 端点。 此类请求将返回 404 Not Found。 这常常令用户感到意外。spring-doc.cadn.net.cn

指定http.securityMatcher()会影响该SecurityFilterChain匹配哪些请求。 然而,这并不会自动影响过滤器链提供的端点。 在这种情况下,您可能需要自定义任何希望过滤器链提供的端点的URL。spring-doc.cadn.net.cn

以下示例演示了如何配置以保护所有以/secured/开头的请求,并拒绝其他所有请求,同时自定义由SecurityFilterChain提供的端点:spring-doc.cadn.net.cn

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生成的页面。 您必须为xref page../authentication/passwords/form.html#servlet-authentication-form-custom提供自己的定制端点。 请注意,Spring Security仍然为您提供了POST /secured/loginPOST /secured/logout端点。spring-doc.cadn.net.cn

真实世界示例

以下示例展示了将这些元素综合在一起的更接近实际世界配置的情况:spring-doc.cadn.net.cn

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 Basic认证。
3 接下来,创建另一个SecurityFilterChain实例,并使用@Order(2),这将被视为第二个过滤链。 此过滤链仅适用于以/accounts//loans//credit-cards//balances/开头的请求。 注意,由于此过滤链是第二个,任何包含/approvals/的请求将匹配之前的过滤链,并且不会被此过滤链匹配。 此过滤链中的请求需要具有ROLE_USER权限。 此过滤链未定义任何认证,因为下一个(默认)过滤链包含了该配置。

spring-doc.cadn.net.cn

4 最后,创建一个额外的 SecurityFilterChain 实例,且不带 @Order 注解。 此配置将处理未被其他过滤器链覆盖的请求,并将最后进行处理(未指定 @Order 默认为最后)。 匹配 //user-login/user-logout/notices/contact/register 的请求允许无需身份验证即可访问。 任何其他请求都要求用户经过身份验证,才能访问未明确允许或未被其他过滤器链保护的 URL。

模块化 HttpSecurityDsl 配置

许多用户偏好将他们的 Spring Security 配置集中在一个地方,并会选择在单个 SecurityFilterChain 实例中进行配置。 然而,在某些情况下,用户可能希望模块化配置。这可以通过以下方式实现:spring-doc.cadn.net.cn

由于 Spring Security Kotlin Dsl (HttpSecurityDsl) 使用了 HttpSecurity,因此所有基于 Java 的 模块化 Bean 自定义 都会在 模块化的 HttpSecurity 配置 之前应用。

HttpSecurityDsl.() → 单元Bean

如果您希望模块化安全配置,可以将逻辑放在一个HttpSecurityDsl.() → Unit Bean 中。 例如,以下配置将确保所有的 HttpSecurityDsl 实例都被配置为:spring-doc.cadn.net.cn

@Bean
fun httpSecurityDslBean(): HttpSecurityDsl.() -> Unit {
    return {
        headers {
            contentSecurityPolicy {
                (1)
                policyDirectives = "object-src 'none'"
            }
        }
        (2)
        redirectToHttps { }
    }
}
1 设置xref page../exploits/headers.html#servlet-headers-csp
2 将所有请求重定向到 https

顶级安全 DSL 颗粒

如果希望进一步模块化你的安全配置,Spring Security 将会自动应用任何顶级的安全 DSL 颗粒。spring-doc.cadn.net.cn

顶级安全DSL可以总结为任何匹配public HttpSecurityDsl.*(<Dsl>)的类DSL。 这相当于任何安全DSL,它是`HttpSecurityDsl`公共方法的一个参数。spring-doc.cadn.net.cn

一些例子可以帮助澄清这一点。 如果ContentTypeOptionsDsl.() → Unit被声明为Bean,它将不会自动应用,因为它是HeadersDsl#contentTypeOptions(ContentTypeOptionsDsl.() → Unit)方法的参数,并且不是HttpSecurityDsl定义的方法的参数。 然而,如果HeadersDsl.() → Unit被声明为Bean,它将自动应用,因为它是HttpSecurityDsl.headers(HeadersDsl.() → Unit)方法的参数。spring-doc.cadn.net.cn

例如,以下配置确保所有HttpSecurityDsl实例都配置为:spring-doc.cadn.net.cn

@Bean
fun headersSecurity(): HeadersDsl.() -> Unit {
    return {
        contentSecurityPolicy {
            (1)
            policyDirectives = "object-src 'none'"
        }
    }
}
1 设置xref page../exploits/headers.html#servlet-headers-csp

DSL Bean 排序

首先,所有模块化 HttpSecurity 配置都会应用,因为 Kotlin Dsl 使用了一个HttpSecurity Bean。spring-doc.cadn.net.cn

第二,每个HttpSecurityDsl.() → Unit Beans 是通过 ObjectProvider#orderedStream() 应用的。这意味着如果存在多个 HttpSecurity.() → Unit Beans,则可以在 Bean 定义中添加 @Order 注解以控制排序。spring-doc.cadn.net.cn

接下来,会查找每种顶级安全 DSL 颗粒度Bean类型,并使用#top-level-dsl-bean对每个进行应用。
如果存在不同类型的顶级安全 Bean(例如:ObjectProvider#orderedStream()HeadersDsl.() → Unit),那么每种 DSL 类型的调用顺序是未定义的。
然而,相同顶级安全 Bean 类型的每个实例通过HttpsRedirectDsl.() → Unit被定义的顺序是可以控制的,并可以通过在 Bean 定义上使用 ObjectProvider#orderedStream() 来实现。spring-doc.cadn.net.cn

最终,HttpSecurityDsl Bean 作为 Bean 注入。 所有 *Dsl.() → Unit Bean 在 HttpSecurityDsl Bean 创建之前被应用。 这允许覆盖 *Dsl.() → Unit Bean 提供的自定义设置。spring-doc.cadn.net.cn

您可以在下面找到一个示例,以说明排序:spring-doc.cadn.net.cn

// 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 所有来自Kotlin DSL的xref page Bean 应用的配置都已生效,因为它使用了模块化Http安全配置。
2 所有 HttpSecurity.() → Unit 实例都会被应用。 adminAuthorization Bean 具有最高的 @Order 注解,因此它会被首先应用。 如果没有在 @Order Beans 上标注 HttpSecurity.() → Unit 注解或者这些注解的值相同,则 @Order 实例的应用顺序是未定义的。
3 userAuthorization 应用于此,因为它是 HttpSecurity.() → Unit 的一个实例。
4 *Dsl.() → Unit 类型的顺序是未定义的。 在此示例中,contentSecurityPolicycontentTypeOptionshttpsRedirect 的顺序是未定义的。 如果将 @Order(Ordered.HIGHEST_PRECEDENCE) 添加到 contentTypeOptions 中,那么我们将知道 contentTypeOptionscontentSecurityPolicy 之前(它们是同一类型),但我们不知道 httpsRedirect 是在 HeadersDsl.() → Unit Bean 之前还是之后。
5 在应用了所有 *Dsl.() → Unit 颗粒之后,HttpSecurityDsl 会被作为颗粒传递进去。