此版本仍在开发中,尚未被视为稳定版本。如需最新稳定版本,请使用 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。