多因素認證

多因素認證 (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.