多因素认证

多因素认证 (MFA) 要求用户提供多种因素才能进行认证。OWASP 将因素分为以下几类:

  • 用户知道的(例如密码)

  • 用户拥有的(例如访问短信或电子邮件)

  • 用户本身的(例如生物识别)

  • 用户所在的(例如地理位置)

  • 用户做的(例如行为分析)

FactorGrantedAuthority

在认证时,Spring Security 的认证机制会添加一个 FactorGrantedAuthority。例如,当用户使用密码进行认证时,一个 FactorGrantedAuthority 会自动添加到 Authentication 中,其 authorityFactorGrantedAuthority.PASSWORD_AUTHORITY。为了在 Spring Security 中要求 MFA,您必须:

  • 指定需要多个因素的授权规则

  • 为每个因素设置认证

@EnableMultiFactorAuthentication

@EnableMultiFactorAuthentication 使启用多因素认证变得容易。下面是一个配置,它为每个授权规则添加了密码和 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_OTTFACTOR_PASSWORDROLE_ADMIN 权限。
2 所有其他 URL 都需要 FACTOR_OTTFACTOR_PASSWORD 权限。
3 设置可以提供所需因素的认证机制。

Spring Security 在幕后根据缺少哪个权限来知道要跳转到哪个端点。如果用户最初使用他们的用户名和密码登录,那么 Spring Security 将重定向到一次性令牌登录页面。如果用户最初使用令牌登录,那么 Spring Security 将重定向到用户名/密码登录页面。

AuthorizationManagerFactory

@EnableMultiFactorAuthenticationauthorities 属性只是发布一个 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<Object> {
    return AuthorizationManagerFactories.multiFactor<Object>()
        .requireFactors(
            FactorGrantedAuthority.PASSWORD_AUTHORITY,
            FactorGrantedAuthority.OTT_AUTHORITY
        )
        .build()
}

选择性地要求 MFA

我们已经演示了如何通过使用 @EnableMultiFactorAuthenticationauthorities 属性来配置整个应用程序以要求 MFA。然而,有时应用程序只希望部分应用程序要求 MFA。考虑以下要求:

  • /admin/ 开头的 URL 应该需要 FACTOR_OTTFACTOR_PASSWORDROLE_ADMIN 权限。

  • /user/settings 开头的 URL 应该需要 FACTOR_OTTFACTOR_PASSWORD 权限。

  • 所有其他 URL 都需要已认证用户。

在这种情况下,有些 URL 需要 MFA,而有些则不需要。这意味着我们之前看到的全局方法不起作用。幸运的是,我们可以使用在 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_OTTFACTOR_PASSWORDROLE_ADMIN
3 明确使用 AuthorizationManagerFactory,以便以 /user/settings 开头的 URL 需要 FACTOR_OTTFACTOR_PASSWORD
4 否则,请求必须经过认证。没有 MFA 要求,因为未使用 AuthorizationManagerFactory
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 有条件地要求因素,可以实现按用户启用 MFA。

  • 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<Object> {
    override fun authorize(
        authentication: Supplier<out Authentication?>, context: Object): 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<Object> {
    val defaults = DefaultAuthorizationManagerFactory<Object>()
    (1)
    defaults.setAdditionalAuthorization(admins)
    (2)
    return defaults
}
1 将自定义 AuthorizationManager 注入为 DefaultAuthorization.additionalAuthorization。这指示 DefaultAuthorizationManagerFactory 任何授权规则都应应用我们的自定义 AuthorizationManager 以及应用程序定义的任何授权要求(例如 `hasRole("ADMIN")`)。
2 DefaultAuthorizationManagerFactory 发布为 Bean,以便它全局使用。

这应该与我们在 AuthorizationManagerFactory 中的前一个示例非常相似。不同之处在于,在前一个示例中,AuthorizationManagerFactoriesDefaultAuthorization.additionalAuthorization 设置为内置的 AuthorizationManager,该管理器始终要求相同的权限。

我们现在可以定义与 AdminMfaAuthorizationManager 结合的授权规则。

  • Java

  • Kotlin

@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
	http
		.authorizeHttpRequests((authorize) -> authorize
			.requestMatchers("/admin/**").hasRole("ADMIN")
			.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_OTTFACTOR_PASSWORD
2 否则,请求必须经过认证。如果用户名是 admin,则还需要 FACTOR_OTTFACTOR_PASSWORD
MFA 是按用户名而不是按角色启用的,因为我们是这样实现 RequiredAuthoritiesAuthorizationManagerConfiguration 的。如果我们愿意,我们可以更改逻辑,根据角色而不是用户名启用 MFA。

RequiredAuthoritiesAuthorizationManager

我们已经演示了如何在 编程式 MFA 中使用自定义 AuthorizationManager 动态确定特定用户的权限。然而,这是一个非常常见的场景,因此 Spring Security 使用 RequiredAuthoritiesAuthorizationManagerRequiredAuthoritiesRepository 提供了内置支持。

让我们使用内置支持来实现与 编程式 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<Object> {
    (1)
    val authorities = MapRequiredAuthoritiesRepository()
    authorities.saveRequiredAuthorities("admin", List.of(
        FactorGrantedAuthority.PASSWORD_AUTHORITY,
        FactorGrantedAuthority.OTT_AUTHORITY)
    )
    (2)
    return RequiredAuthoritiesAuthorizationManager(authorities)
}
1 创建一个 MapRequiredAuthoritiesRepository,它将用户名为 admin 的用户映射到需要 MFA。
2 返回一个注入了 MapRequiredAuthoritiesRepositoryRequiredAuthoritiesAuthorizationManager

接下来,我们可以定义一个使用 RequiredAuthoritiesAuthorizationManagerAuthorizationManagerFactory

  • 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<Object>): AuthorizationManagerFactory<Object> {
    val defaults = DefaultAuthorizationManagerFactory<Object>()
    (1)
    defaults.setAdditionalAuthorization(admins)
    (2)
    return defaults
}
1 RequiredAuthoritiesAuthorizationManager 注入为 DefaultAuthorization.additionalAuthorization。这指示 DefaultAuthorizationManagerFactory 任何授权规则都应应用 RequiredAuthoritiesAuthorizationManager 以及应用程序定义的任何授权要求(例如 `hasRole("ADMIN")`)。
2 DefaultAuthorizationManagerFactory 发布为 Bean,以便它全局使用。

我们现在可以定义与 RequiredAuthoritiesAuthorizationManager 结合的授权规则。include-code::./RequiredAuthoritiesAuthorizationManagerConfiguration[tag=httpSecurity,indent=0] <1> 以 /admin/** 开头的 URL 需要 ROLE_ADMIN。如果用户名是 admin,则还需要 FACTOR_OTTFACTOR_PASSWORD。 <2> 否则,请求必须经过认证。如果用户名是 admin,则还需要 FACTOR_OTTFACTOR_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_PASSWORDFACTOR_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_OTTFACTOR_PASSWORDROLE_ADMIN
2 对于所有其他 URL,需要以下权限:FACTOR_OTTFACTOR_PASSWORDROLE_USER
3 设置可以提供所需因素的认证机制。

此配置只指定了两个授权规则,但这足以看出重复是不合乎需要的。你能想象声明数百个这样的规则会是什么样子吗?

更重要的是,表达更复杂的授权规则变得困难。例如,您如何要求两个因素以及 ROLE_ADMINROLE_USER

这些问题的答案,正如我们已经看到的,是使用 [egmfa]

重新认证

其中最常见的是重新认证。设想一个按以下方式配置的应用程序:

  • 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/** 端点都需要一次性令牌登录才能获得授权。

在上述配置下,用户可以使用您支持的任何机制登录。如果他们想访问个人资料页面,Spring Security 将重定向他们到一次性令牌登录页面以获取它。

通过这种方式,赋予用户的权限与提供的证明数量成正比。这种自适应方法允许用户只提供执行其预期操作所需的证明。

授权更多范围

您还可以配置异常处理,以指导 Spring Security 如何获取缺失的范围。

考虑一个应用程序,它对给定端点需要特定的 OAuth 2.0 范围

  • 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<Object> {
    return AuthorizationManagerFactories.multiFactor<Object>()
            .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()
}
© . This site is unofficial and not affiliated with VMware.