|
此版本仍在开发中,尚未被视为稳定版本。如需最新稳定版本,请使用 Spring Security 7.0.4! |
多因素认证
多因素认证(MFA)要求用户在认证时提供多个因素。 OWASP 将这些因素划分为以下类别:
-
用户所知道的信息(例如密码)
-
用户拥有的某种东西(例如访问短信或电子邮件的权限)
-
你所具有的某种特征(例如生物识别信息)
-
您所在的位置(例如地理定位)
-
你所执行的操作(例如:行为分析)
FactorGrantedAuthority
在身份验证时,Spring Security 的身份验证机制会添加一个 FactorGrantedAuthority。
例如,当用户使用密码进行身份验证时,系统会自动将带有 authority 值的 FactorGrantedAuthority 添加到 Authentication 中。
若要在 Spring Security 中要求使用多因素认证(MFA),您必须:
-
指定一条需要多重因素的身份验证规则
-
为每个因素设置身份验证
启用多因素认证
@EnableMultiFactorAuthentication 使得启用多因素认证变得简单。
下方是一个配置示例,该配置为所有授权规则添加了密码和一次性Tokens(OTT)的双重要求。
-
Java
-
Kotlin
@EnableMultiFactorAuthentication(authorities = {
FactorGrantedAuthority.PASSWORD_AUTHORITY,
FactorGrantedAuthority.OTT_AUTHORITY })
@EnableMultiFactorAuthentication( authorities = [
FactorGrantedAuthority.PASSWORD_AUTHORITY,
FactorGrantedAuthority.OTT_AUTHORITY])
我们现在能够简洁地创建一种始终要求多因素认证的配置。
-
Java
-
Kotlin
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests((authorize) -> authorize
(1)
.requestMatchers("/admin/**").hasRole("ADMIN")
(2)
.anyRequest().authenticated()
)
(3)
.formLogin(Customizer.withDefaults())
.oneTimeTokenLogin(Customizer.withDefaults());
return http.build();
}
@Bean
fun securityFilterChain(http: HttpSecurity): SecurityFilterChain? {
http {
authorizeHttpRequests {
(1)
authorize("/admin/**", hasRole("ADMIN"))
(2)
authorize(anyRequest, authenticated)
}
(3)
formLogin { }
oneTimeTokenLogin { }
}
return http.build()
}
| 1 | 以 /admin/** 开头的 URL 需要具备 FACTOR_OTT、FACTOR_PASSWORD 和 ROLE_ADMIN 权限。 |
| 2 | 所有其他 URL 都需要权限 FACTOR_OTT、FACTOR_PASSWORD |
| 3 | 设置能够提供所需认证因素的身份验证机制。 |
Spring Security 在幕后会根据缺失的权限自动判断应跳转到哪个端点。 如果用户最初使用用户名和密码登录,则 Spring Security 会重定向到一次性Tokens(One-Time-Token)登录页面。 如果用户最初使用Tokens登录,则 Spring Security 会重定向到用户名/密码登录页面。
授权管理器工厂
@EnableMultiFactorAuthentication authorities 属性只是发布 AuthorizationManagerFactory Bean 的快捷方式。
当 AuthorizationManagerFactory Bean 可用时,Spring Security 会利用它来创建授权规则,例如在 AuthorizationManagerFactory Bean 接口上定义的 hasAnyRole(String)。
由 @EnableMultiFactorAuthentication 发布的实现将确保每个授权都与指定因素的要求相结合。
下面的 AuthorizationManagerFactory Bean 是在之前讨论的 @EnableMultiFactorAuthentication 示例中发布的。
-
Java
-
Kotlin
@Bean
AuthorizationManagerFactory<Object> authz() {
return AuthorizationManagerFactories.multiFactor()
.requireFactors(
FactorGrantedAuthority.PASSWORD_AUTHORITY,
FactorGrantedAuthority.OTT_AUTHORITY
)
.build();
}
@Bean
fun authz(): AuthorizationManagerFactory<Any> {
return AuthorizationManagerFactories.multiFactor<Any>()
.requireFactors(
FactorGrantedAuthority.PASSWORD_AUTHORITY,
FactorGrantedAuthority.OTT_AUTHORITY
)
.build()
}
选择性要求多因素认证
我们已演示了如何使用 @EnableMultiFactorAuthentications authorities 属性将整个应用程序配置为要求多因素认证(MFA)。
然而,有时应用程序仅希望部分功能要求 MFA。
请考虑以下需求:
-
以
/admin/开头的 URL 应要求具备FACTOR_OTT、FACTOR_PASSWORD和ROLE_ADMIN权限。 -
以
/user/settings开头的 URL 应要求具备FACTOR_OTT和FACTOR_PASSWORD权限。 -
其他所有 URL 都需要经过身份验证的用户
在这种情况下,某些 URL 需要多因素认证(MFA),而其他 URL 则不需要。 这意味着我们之前看到的全局方法不再适用。 幸运的是,我们可以利用在 AuthorizationManagerFactory 中学到的知识,以一种简洁的方式来解决这个问题。
首先指定 @EnableMultiFactorAuthentication 注解,但不配置任何权限。
这样做可以启用 MFA(多因素认证)支持,但不会发布任何 AuthorizationManagerFactory Bean。
-
Java
-
Kotlin
@EnableMultiFactorAuthentication(authorities = {})
@EnableMultiFactorAuthentication(authorities = [])
接下来创建一个 AuthorizationManagerFactory 实例,但不要将其发布为 Bean。
-
Java
-
Kotlin
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
(1)
var mfa = AuthorizationManagerFactories.multiFactor()
.requireFactors(
FactorGrantedAuthority.PASSWORD_AUTHORITY,
FactorGrantedAuthority.OTT_AUTHORITY
)
.build();
http
.authorizeHttpRequests((authorize) -> authorize
(2)
.requestMatchers("/admin/**").access(mfa.hasRole("ADMIN"))
(3)
.requestMatchers("/user/settings/**").access(mfa.authenticated())
(4)
.anyRequest().authenticated()
)
(5)
.formLogin(Customizer.withDefaults())
.oneTimeTokenLogin(Customizer.withDefaults());
return http.build();
}
@Bean
@Throws(Exception::class)
fun securityFilterChain(http: HttpSecurity): SecurityFilterChain? {
(1)
val mfa = AuthorizationManagerFactories.multiFactor<Any>()
.requireFactors(
FactorGrantedAuthority.PASSWORD_AUTHORITY,
FactorGrantedAuthority.OTT_AUTHORITY
)
.build()
http {
authorizeHttpRequests {
(2)
authorize("/admin/**", mfa.hasRole("ADMIN"))
(3)
authorize("/user/settings/**", mfa.authenticated())
(4)
authorize(anyRequest, authenticated)
}
(5)
formLogin { }
oneTimeTokenLogin { }
}
return http.build()
}
| 1 | 像之前那样创建一个 DefaultAuthorizationManagerFactory,但不要将其发布为 Bean。
通过不将其发布为 Bean,我们可以有选择地使用 AuthorizationManagerFactory,而不是将其用于每一条授权规则。 |
| 2 | 显式使用 AuthorizationManagerFactory,以便以 /admin/** 开头的 URL 需要具备 FACTOR_OTT、FACTOR_PASSWORD 和 ROLE_ADMIN 权限。 |
| 3 | 显式使用 AuthorizationManagerFactory,以便以 /user/settings 开头的 URL 需要 FACTOR_OTT 和 FACTOR_PASSWORD |
| 4 | 否则,该请求必须经过身份验证。
由于未使用 AuthorizationManagerFactory,因此没有多因素认证(MFA)要求。 |
| 5 | 设置能够提供所需认证因素的身份验证机制。 |
指定有效持续时间
有时,我们可能希望根据最近一次身份验证的时间来定义授权规则。
例如,某个应用程序可能要求用户必须在过去一小时内完成身份验证,才能允许访问 /user/settings 端点。
请记住,在身份验证时,会向 FactorGrantedAuthority 添加一个 Authentication。
FactorGrantedAuthority 指明了其 issuedAt(签发时间),但并未说明其有效时长。
这是有意为之的设计,因为它允许同一个 FactorGrantedAuthority 与不同的 validDuration(有效持续时间)一起使用。
让我们来看一个示例,说明如何满足以下要求:
-
以
/admin/开头的 URL 应要求用户在最近 30 分钟内已提供过密码 -
以
/user/settings开头的 URL 应要求用户在过去一小时内已提供密码 -
否则,需要进行身份验证,但它并不关心使用的是密码还是身份验证发生在多久以前
-
Java
-
Kotlin
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
(1)
var passwordIn30m = AuthorizationManagerFactories.multiFactor()
.requireFactor( (factor) -> factor
.passwordAuthority()
.validDuration(Duration.ofMinutes(30))
)
.build();
(2)
var passwordInHour = AuthorizationManagerFactories.multiFactor()
.requireFactor( (factor) -> factor
.passwordAuthority()
.validDuration(Duration.ofHours(1))
)
.build();
http
.authorizeHttpRequests((authorize) -> authorize
(3)
.requestMatchers("/admin/**").access(passwordIn30m.hasRole("ADMIN"))
(4)
.requestMatchers("/user/settings/**").access(passwordInHour.authenticated())
(5)
.anyRequest().authenticated()
)
(6)
.formLogin(Customizer.withDefaults());
return http.build();
}
@Bean
@Throws(Exception::class)
fun securityFilterChain(http: HttpSecurity): SecurityFilterChain? {
(1)
val passwordIn30m = AuthorizationManagerFactories.multiFactor<Any>()
.requireFactor( { factor -> factor
.passwordAuthority()
.validDuration(Duration.ofMinutes(30))
})
.build()
(2)
val passwordInHour = AuthorizationManagerFactories.multiFactor<Any>()
.requireFactor( { factor -> factor
.passwordAuthority()
.validDuration(Duration.ofHours(1))
})
.build()
http {
authorizeHttpRequests {
(3)
authorize("/admin/**", passwordIn30m.hasRole("ADMIN"))
(4)
authorize("/user/settings/**", passwordInHour.authenticated())
(5)
authorize(anyRequest, authenticated)
}
(6)
formLogin { }
}
return http.build()
}
| 1 | 首先,我们将 passwordIn30m 定义为一个在30分钟内有效的密码要求 |
| 2 | 接下来,我们将 passwordInHour 定义为一小时内密码的一个要求 |
| 3 | 我们使用 passwordIn30m 来要求以 /admin/ 开头的 URL 必须在最近 30 分钟内提供过密码,并且用户必须拥有 ROLE_ADMIN 权限。 |
| 4 | 我们使用 passwordInHour 来要求以 /user/settings 开头的 URL 必须在最近一小时内提供过密码。 |
| 5 | 否则,需要进行身份验证,但它并不关心使用的是密码还是身份验证发生在多久以前 |
| 6 | 设置能够提供所需认证因素的身份验证机制。 |
编程式多因素认证
在我们之前的示例中,MFA(多因素认证)是针对每个请求的静态决策。
有时我们可能希望对某些用户要求 MFA,而对其他用户则不要求。
要实现基于每个用户判断是否启用 MFA,可以创建一个自定义的 AuthorizationManager,该管理器根据 Authentication 对象有条件地要求多因素认证。
-
Java
-
Kotlin
@Component
class AdminMfaAuthorizationManager implements AuthorizationManager<Object> {
@Override
public AuthorizationResult authorize(Supplier<? extends @Nullable Authentication> authentication, Object context) {
if ("admin".equals(authentication.get().getName())) {
AuthorizationManager<Object> admins =
AllAuthoritiesAuthorizationManager.hasAllAuthorities(
FactorGrantedAuthority.OTT_AUTHORITY,
FactorGrantedAuthority.PASSWORD_AUTHORITY
);
(1)
return admins.authorize(authentication, context);
} else {
(2)
return new AuthorizationDecision(true);
}
}
}
@Component
internal open class AdminMfaAuthorizationManager : AuthorizationManager<Any> {
override fun authorize(
authentication: Supplier<out Authentication?>, context: Any): AuthorizationResult {
return if ("admin" == authentication.get().name) {
var admins =
AllAuthoritiesAuthorizationManager.hasAllAuthorities<Any>(
FactorGrantedAuthority.OTT_AUTHORITY,
FactorGrantedAuthority.PASSWORD_AUTHORITY)
(1)
admins.authorize(authentication, context)
} else {
(2)
AuthorizationDecision(true)
}
}
}
| 1 | 用户 admin 需要启用多因素认证(MFA) |
| 2 | 否则,不需要多重身份验证(MFA) |
要全局启用多因素认证(MFA)规则,我们可以发布一个 AuthorizationManagerFactory Bean。
-
Java
-
Kotlin
@Bean
AuthorizationManagerFactory<Object> authorizationManagerFactory(
AdminMfaAuthorizationManager admins) {
DefaultAuthorizationManagerFactory<Object> defaults = new DefaultAuthorizationManagerFactory<>();
(1)
defaults.setAdditionalAuthorization(admins);
(2)
return defaults;
}
@Bean
fun authorizationManagerFactory(admins: AdminMfaAuthorizationManager): AuthorizationManagerFactory<Any> {
val defaults = DefaultAuthorizationManagerFactory<Any>()
(1)
defaults.setAdditionalAuthorization(admins)
(2)
return defaults
}
| 1 | 将自定义的 AuthorizationManager 注入为 DefaultAuthorization.additionalAuthorization。
这会指示 DefaultAuthorizationManagerFactory:任何授权规则都应同时应用我们自定义的 AuthorizationManager 以及应用程序定义的任何授权要求(例如 hasRole("ADMIN"))。 |
| 2 | 将 DefaultAuthorizationManagerFactory 发布为一个 Bean,以便全局使用 |
这应该与我们之前在AuthorizationManagerFactory中的示例非常相似。
不同之处在于,在之前的示例中,AuthorizationManagerFactories使用了一个内置的DefaultAuthorization.additionalAuthorization来设置AuthorizationManager,该4始终要求相同的权限。
我们现在可以定义我们的授权规则,这些规则将与 AdminMfaAuthorizationManager 结合使用。
-
Java
-
Kotlin
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests((authorize) -> authorize
(1)
.requestMatchers("/admin/**").hasRole("ADMIN")
(2)
.anyRequest().authenticated()
)
.formLogin(Customizer.withDefaults())
.oneTimeTokenLogin(Customizer.withDefaults());
return http.build();
}
@Bean
fun securityFilterChain(http: HttpSecurity): SecurityFilterChain? {
http {
authorizeHttpRequests {
(1)
authorize("/admin/**", hasRole("ADMIN"))
(2)
authorize(anyRequest, authenticated)
}
formLogin { }
oneTimeTokenLogin { }
}
return http.build()
}
| 1 | 以 /admin/** 开头的 URL 需要 ROLE_ADMIN 权限。
如果用户名为 admin,则还需要 FACTOR_OTT 和 FACTOR_PASSWORD。 |
| 2 | 否则,该请求必须经过身份验证。
如果用户名为admin,则还必须提供FACTOR_OTT和FACTOR_PASSWORD。 |
MFA 是根据用户名而非角色启用的,因为这是我们实现 RequiredAuthoritiesAuthorizationManagerConfiguration 的方式。
如果我们愿意,也可以修改逻辑,改为根据角色而不是用户名来启用 MFA。 |
所需权限授权管理器
我们已演示了如何在编程式多因素认证中使用自定义AuthorizationManager动态确定特定用户的权限。
然而,这是一个非常常见的场景,因此 Spring Security 提供了内置支持,使用RequiredAuthoritiesAuthorizationManager和RequiredAuthoritiesRepository。
让我们使用内置支持来实现与编程式 MFA中相同的需求。
我们首先创建要使用的 RequiredAuthoritiesAuthorizationManager Bean。
-
Java
-
Kotlin
@Bean
RequiredAuthoritiesAuthorizationManager<Object> adminAuthorization() {
(1)
MapRequiredAuthoritiesRepository authorities = new MapRequiredAuthoritiesRepository();
authorities.saveRequiredAuthorities("admin", List.of(
FactorGrantedAuthority.PASSWORD_AUTHORITY,
FactorGrantedAuthority.OTT_AUTHORITY)
);
(2)
return new RequiredAuthoritiesAuthorizationManager<>(authorities);
}
@Bean
fun adminAuthorization(): RequiredAuthoritiesAuthorizationManager<Any> {
(1)
val authorities = MapRequiredAuthoritiesRepository()
authorities.saveRequiredAuthorities("admin", listOf(
FactorGrantedAuthority.PASSWORD_AUTHORITY,
FactorGrantedAuthority.OTT_AUTHORITY)
)
(2)
return RequiredAuthoritiesAuthorizationManager(authorities)
}
| 1 | 创建一个 MapRequiredAuthoritiesRepository,将用户名 admin 的用户映射为需要多因素认证(MFA)。 |
| 2 | 返回一个注入了 RequiredAuthoritiesAuthorizationManager 的 MapRequiredAuthoritiesRepository。 |
接下来,我们可以定义一个使用 AuthorizationManagerFactory 的 RequiredAuthoritiesAuthorizationManager。
-
Java
-
Kotlin
@Bean
AuthorizationManagerFactory<Object> authorizationManagerFactory(
RequiredAuthoritiesAuthorizationManager admins) {
DefaultAuthorizationManagerFactory<Object> defaults = new DefaultAuthorizationManagerFactory<>();
(1)
defaults.setAdditionalAuthorization(admins);
(2)
return defaults;
}
@Bean
fun authorizationManagerFactory(admins: RequiredAuthoritiesAuthorizationManager<Any>): AuthorizationManagerFactory<Any> {
val defaults = DefaultAuthorizationManagerFactory<Any>()
(1)
defaults.setAdditionalAuthorization(admins)
(2)
return defaults
}
| 1 | 将 RequiredAuthoritiesAuthorizationManager 注入为 DefaultAuthorization.additionalAuthorization。
这会指示 DefaultAuthorizationManagerFactory:任何授权规则都应同时应用 RequiredAuthoritiesAuthorizationManager 以及应用程序定义的任何授权要求(例如 hasRole("ADMIN"))。 |
| 2 | 将 DefaultAuthorizationManagerFactory 发布为一个 Bean,以便全局使用 |
我们现在可以定义我们的授权规则,这些规则将与 RequiredAuthoritiesAuthorizationManager 结合使用。
-
Java
-
Kotlin
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests((authorize) -> authorize
.requestMatchers("/admin/**").hasRole("ADMIN") (1)
.anyRequest().authenticated() (2)
)
.formLogin(Customizer.withDefaults())
.oneTimeTokenLogin(Customizer.withDefaults());
return http.build();
}
@Bean
fun securityFilterChain(http: HttpSecurity): SecurityFilterChain? {
http {
authorizeHttpRequests {
authorize("/admin/**", hasRole("ADMIN")) (1)
authorize(anyRequest, authenticated) (2)
}
formLogin { }
oneTimeTokenLogin { }
}
return http.build()
}
| 1 | 以 /admin/** 开头的 URL 需要 ROLE_ADMIN 权限。
如果用户名为 admin,则还需要 FACTOR_OTT 和 FACTOR_PASSWORD。 |
| 2 | 否则,该请求必须经过身份验证。
如果用户名为admin,则还必须提供FACTOR_OTT和FACTOR_PASSWORD。 |
我们的示例使用了用户名到所需额外权限的内存映射。
对于可以根据用户名确定的更动态的用例,可以创建 RequiredAuthoritiesRepository 的自定义实现。
可能的示例包括在显式设置中查找用户是否启用了 MFA、确定用户是否注册了通行密钥等。
对于需要根据Authentication来确定多因素认证(MFA)的情况,可以使用自定义的AuthorizationManger,如编程式 MFA中所示。
使用 hasAllAuthorities
我们已经展示了大量用于支持多因素认证(MFA)的附加基础设施。
然而,对于简单的 MFA 使用场景,使用 hasAllAuthorities 来要求多个认证因素是有效的。
-
Java
-
Kotlin
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests((authorize) -> authorize
(1)
.anyRequest().hasAllAuthorities(
FactorGrantedAuthority.PASSWORD_AUTHORITY,
FactorGrantedAuthority.OTT_AUTHORITY
)
)
(2)
.formLogin(Customizer.withDefaults())
.oneTimeTokenLogin(Customizer.withDefaults());
return http.build();
}
@Bean
fun securityFilterChain(http: HttpSecurity): SecurityFilterChain? {
http {
authorizeHttpRequests {
(1)
authorize(anyRequest, hasAllAuthorities(
FactorGrantedAuthority.PASSWORD_AUTHORITY,
FactorGrantedAuthority.OTT_AUTHORITY
))
}
(2)
formLogin { }
oneTimeTokenLogin { }
}
return http.build()
}
| 1 | 每个请求都需要 FACTOR_PASSWORD 和 FACTOR_OTT |
| 2 | 设置能够提供所需认证因素的身份验证机制。 |
上述配置仅适用于最简单的使用场景。 如果你有很多端点,可能不希望在每个授权规则中都重复多因素认证(MFA)的要求。
例如,请考虑以下配置:
-
Java
-
Kotlin
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests((authorize) -> authorize
(1)
.requestMatchers("/admin/**").hasAllAuthorities(
"ROLE_ADMIN",
FactorGrantedAuthority.PASSWORD_AUTHORITY,
FactorGrantedAuthority.OTT_AUTHORITY
)
(2)
.anyRequest().hasAllAuthorities(
"ROLE_USER",
FactorGrantedAuthority.PASSWORD_AUTHORITY,
FactorGrantedAuthority.OTT_AUTHORITY
)
)
(3)
.formLogin(Customizer.withDefaults())
.oneTimeTokenLogin(Customizer.withDefaults());
return http.build();
}
@Bean
fun securityFilterChain(http: HttpSecurity): SecurityFilterChain? {
http {
authorizeHttpRequests {
(1)
authorize("/admin/**", hasAllAuthorities(
"ROLE_ADMIN",
FactorGrantedAuthority.PASSWORD_AUTHORITY,
FactorGrantedAuthority.OTT_AUTHORITY
))
(2)
authorize(anyRequest, hasAllAuthorities(
"ROLE_USER",
FactorGrantedAuthority.PASSWORD_AUTHORITY,
FactorGrantedAuthority.OTT_AUTHORITY
))
}
(3)
formLogin { }
oneTimeTokenLogin { }
}
return http.build()
}
| 1 | 对于以 /admin/** 开头的 URL,需要具备以下权限:FACTOR_OTT、FACTOR_PASSWORD 和 ROLE_ADMIN。 |
| 2 | 对于所有其他 URL,需要以下权限:FACTOR_OTT、FACTOR_PASSWORD、ROLE_USER。 |
| 3 | 设置能够提供所需认证因素的身份验证机制。 |
该配置仅指定了两条授权规则,但足以看出这种重复是不可取的。 你能想象一下像这样声明数百条规则会是什么样子吗?
更进一步,要表达更复杂的授权规则也变得困难。
例如,你该如何要求双因素认证,并且同时具备 ROLE_ADMIN 或 ROLE_USER 角色?
正如我们已经看到的那样,这些问题的答案是使用 @EnableMultiFactorAuthentication
Re-authentication
其中最常见的是重新认证。 假设一个应用程序按如下方式配置:
-
Java
-
Kotlin
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests((authorize) -> authorize.anyRequest().authenticated())
.formLogin(Customizer.withDefaults())
.oneTimeTokenLogin(Customizer.withDefaults());
return http.build();
}
@Bean
fun securityFilterChain(http: HttpSecurity): SecurityFilterChain? {
http {
authorizeHttpRequests {
authorize(anyRequest, authenticated)
}
formLogin { }
oneTimeTokenLogin { }
}
return http.build()
}
默认情况下,此应用程序启用了两种身份验证机制,这意味着用户可以使用其中任意一种机制完成完全身份验证。
如果有一组端点需要特定的因素,我们可以在 authorizeHttpRequests 中按如下方式指定:
-
Java
-
Kotlin
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests((authorize) -> authorize
.requestMatchers("/profile/**").hasAuthority(FactorGrantedAuthority.OTT_AUTHORITY) (1)
.anyRequest().authenticated()
)
.formLogin(Customizer.withDefaults())
.oneTimeTokenLogin(Customizer.withDefaults());
return http.build();
}
@Bean
fun securityFilterChain(http: HttpSecurity): SecurityFilterChain? {
http {
authorizeHttpRequests {
authorize("/profile/**", hasAuthority(FactorGrantedAuthority.OTT_AUTHORITY)) (1)
authorize(anyRequest, authenticated)
}
formLogin { }
oneTimeTokenLogin { }
}
return http.build()
}
| 1 | 声明所有 /profile/** 端点都需要通过一次性Tokens登录才能获得授权 |
根据上述配置,用户可以使用您支持的任何机制进行登录。 而且,如果他们想要访问个人资料页面,Spring Security 将会把他们重定向到一次性Tokens(One-Time-Token)登录页面以获取该Tokens。
通过这种方式,授予用户的权限与其所提供的证明量成正比。 这种自适应方法允许用户仅提供执行其预期操作所需的证明。
授权更多范围
您还可以配置异常处理,以指导 Spring Security 如何获取缺失的 scope。
考虑一个应用程序,它要求为某个端点指定特定的 OAuth 2.0 范围(scope):
-
Java
-
Kotlin
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests((authorize) -> authorize
.requestMatchers("/profile/**").hasAuthority("SCOPE_profile:read")
.anyRequest().authenticated()
)
.x509(Customizer.withDefaults())
.oauth2Login(Customizer.withDefaults());
return http.build();
}
@Bean
fun securityFilterChain(http: HttpSecurity): SecurityFilterChain? {
http {
authorizeHttpRequests {
authorize("/profile/**", hasAuthority("SCOPE_profile:read"))
authorize(anyRequest, authenticated)
}
x509 { }
oauth2Login { }
}
return http.build()
}
如果这也配置了一个像下面这样的 AuthorizationManagerFactory bean:
-
Java
-
Kotlin
@Bean
AuthorizationManagerFactory<Object> authz() {
return AuthorizationManagerFactories.multiFactor()
.requireFactors(FactorGrantedAuthority.X509_AUTHORITY, FactorGrantedAuthority.AUTHORIZATION_CODE_AUTHORITY)
.build();
}
@Bean
fun authz(): AuthorizationManagerFactory<Any> {
return AuthorizationManagerFactories.multiFactor<Any>()
.requireFactors(
FactorGrantedAuthority.X509_AUTHORITY,
FactorGrantedAuthority.AUTHORIZATION_CODE_AUTHORITY
)
.build()
}
然后,应用程序将需要一个 X.509 证书以及来自 OAuth 2.0 授权服务器的授权。
如果用户不同意 profile:read 权限,当前应用程序将返回 403 错误。
然而,如果你的应用程序有办法重新请求用户授权,那么你可以像下面这样在 AuthenticationEntryPoint 中实现该逻辑:
-
Java
-
Kotlin
@Component
class ScopeRetrievingAuthenticationEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException)
throws IOException, ServletException {
response.sendRedirect("https://authz.example.org/authorize?scope=profile:read");
}
}
@Component
internal class ScopeRetrievingAuthenticationEntryPoint : AuthenticationEntryPoint {
override fun commence(request: HttpServletRequest, response: HttpServletResponse, authException: AuthenticationException) {
response.sendRedirect("https://authz.example.org/authorize?scope=profile:read")
}
}
然后,您的过滤器链声明可以将此入口点绑定到指定的权限,如下所示:
-
Java
-
Kotlin
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http, ScopeRetrievingAuthenticationEntryPoint oauth2) throws Exception {
http
.authorizeHttpRequests((authorize) -> authorize
.requestMatchers("/profile/**").hasAuthority("SCOPE_profile:read")
.anyRequest().authenticated()
)
.x509(Customizer.withDefaults())
.oauth2Login(Customizer.withDefaults())
.exceptionHandling((exceptions) -> exceptions
.defaultDeniedHandlerForMissingAuthority(oauth2, "SCOPE_profile:read")
);
return http.build();
}
@Bean
fun securityFilterChain(http: HttpSecurity, oauth2: ScopeRetrievingAuthenticationEntryPoint): DefaultSecurityFilterChain? {
http {
authorizeHttpRequests {
authorize("/profile/**", hasAuthority("SCOPE_profile:read"))
authorize(anyRequest, authenticated)
}
x509 { }
oauth2Login { }
}
http.exceptionHandling { e: ExceptionHandlingConfigurer<HttpSecurity> -> e
.defaultDeniedHandlerForMissingAuthority(oauth2, "SCOPE_profile:read")
}
return http.build()
}