多因素认证
多因素认证 (MFA) 要求用户提供多种因素才能进行认证。OWASP 将因素分为以下几类:
-
用户知道的(例如密码)
-
用户拥有的(例如访问短信或电子邮件)
-
用户本身的(例如生物识别)
-
用户所在的(例如地理位置)
-
用户做的(例如行为分析)
FactorGrantedAuthority
在认证时,Spring Security 的认证机制会添加一个 FactorGrantedAuthority。例如,当用户使用密码进行认证时,一个 FactorGrantedAuthority 会自动添加到 Authentication 中,其 authority 为 FactorGrantedAuthority.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_OTT、FACTOR_PASSWORD、ROLE_ADMIN 权限。 |
| 2 | 所有其他 URL 都需要 FACTOR_OTT、FACTOR_PASSWORD 权限。 |
| 3 | 设置可以提供所需因素的认证机制。 |
Spring Security 在幕后根据缺少哪个权限来知道要跳转到哪个端点。如果用户最初使用他们的用户名和密码登录,那么 Spring Security 将重定向到一次性令牌登录页面。如果用户最初使用令牌登录,那么 Spring Security 将重定向到用户名/密码登录页面。
AuthorizationManagerFactory
@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<Object> {
return AuthorizationManagerFactories.multiFactor<Object>()
.requireFactors(
FactorGrantedAuthority.PASSWORD_AUTHORITY,
FactorGrantedAuthority.OTT_AUTHORITY
)
.build()
}
选择性地要求 MFA
我们已经演示了如何通过使用 @EnableMultiFactorAuthentication 的 authorities 属性来配置整个应用程序以要求 MFA。然而,有时应用程序只希望部分应用程序要求 MFA。考虑以下要求:
-
以
/admin/开头的 URL 应该需要FACTOR_OTT、FACTOR_PASSWORD、ROLE_ADMIN权限。 -
以
/user/settings开头的 URL 应该需要FACTOR_OTT、FACTOR_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_OTT、FACTOR_PASSWORD 和 ROLE_ADMIN。 |
| 3 | 明确使用 AuthorizationManagerFactory,以便以 /user/settings 开头的 URL 需要 FACTOR_OTT 和 FACTOR_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 中的前一个示例非常相似。不同之处在于,在前一个示例中,AuthorizationManagerFactories 将 DefaultAuthorization.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_OTT 和 FACTOR_PASSWORD。 |
| 2 | 否则,请求必须经过认证。如果用户名是 admin,则还需要 FACTOR_OTT 和 FACTOR_PASSWORD。 |
MFA 是按用户名而不是按角色启用的,因为我们是这样实现 RequiredAuthoritiesAuthorizationManagerConfiguration 的。如果我们愿意,我们可以更改逻辑,根据角色而不是用户名启用 MFA。 |
RequiredAuthoritiesAuthorizationManager
我们已经演示了如何在 编程式 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<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 | 返回一个注入了 MapRequiredAuthoritiesRepository 的 RequiredAuthoritiesAuthorizationManager。 |
接下来,我们可以定义一个使用 RequiredAuthoritiesAuthorizationManager 的 AuthorizationManagerFactory。
-
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_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?
这些问题的答案,正如我们已经看到的,是使用 [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()
}