組態模型

預設組態

OAuth2AuthorizationServerConfiguration 是一個 @Configuration,為 OAuth2 授權伺服器提供最簡化的預設組態。

OAuth2AuthorizationServerConfiguration 使用 OAuth2AuthorizationServerConfigurer 套用預設組態,並註冊一個 SecurityFilterChain @Bean,其中包含所有支援 OAuth2 授權伺服器的基礎架構組件。

OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(HttpSecurity) 是一個便利的 (static) 輔助方法,可將預設的 OAuth2 安全組態套用至 HttpSecurity

OAuth2 授權伺服器 SecurityFilterChain @Bean 配置了以下預設協定端點

JWK 集合端點僅在註冊 JWKSource<SecurityContext> @Bean 時才會配置。

以下範例示範如何使用 OAuth2AuthorizationServerConfiguration 套用最簡化的預設組態

@Configuration
@Import(OAuth2AuthorizationServerConfiguration.class)
public class AuthorizationServerConfig {

	@Bean
	public RegisteredClientRepository registeredClientRepository() {
		List<RegisteredClient> registrations = ...
		return new InMemoryRegisteredClientRepository(registrations);
	}

	@Bean
	public JWKSource<SecurityContext> jwkSource() {
		RSAKey rsaKey = ...
		JWKSet jwkSet = new JWKSet(rsaKey);
		return (jwkSelector, securityContext) -> jwkSelector.select(jwkSet);
	}

}
authorization_code 授權類型需要資源擁有者通過驗證。因此,除了預設的 OAuth2 安全組態之外,還必須配置使用者驗證機制。

預設組態中停用了 OpenID Connect 1.0。以下範例示範如何透過初始化 OidcConfigurer 來啟用 OpenID Connect 1.0

@Bean
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
	OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);

	http.getConfigurer(OAuth2AuthorizationServerConfigurer.class)
		.oidc(Customizer.withDefaults());	// Initialize `OidcConfigurer`

	return http.build();
}

除了預設協定端點外,OAuth2 授權伺服器 SecurityFilterChain @Bean 也配置了以下 OpenID Connect 1.0 協定端點

OpenID Connect 1.0 用戶端註冊端點 預設停用,因為許多部署不需要動態用戶端註冊。
OAuth2AuthorizationServerConfiguration.jwtDecoder(JWKSource<SecurityContext>) 是一個便利的 (static) 輔助方法,可用於註冊 JwtDecoder @Bean,這是 OpenID Connect 1.0 UserInfo 端點OpenID Connect 1.0 用戶端註冊端點REQUIRED(必要) 項目。

以下範例示範如何註冊 JwtDecoder @Bean

@Bean
public JwtDecoder jwtDecoder(JWKSource<SecurityContext> jwkSource) {
	return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource);
}

OAuth2AuthorizationServerConfiguration 的主要目的是提供一個便利的方法,為 OAuth2 授權伺服器套用最簡化的預設組態。然而,在大多數情況下,將需要自訂組態。

自訂組態

OAuth2AuthorizationServerConfigurer 提供完整自訂 OAuth2 授權伺服器安全組態的能力。它讓您指定要使用的核心組件 - 例如,RegisteredClientRepositoryOAuth2AuthorizationServiceOAuth2TokenGenerator 等。此外,它還讓您自訂協定端點的請求處理邏輯 – 例如,授權端點裝置授權端點裝置驗證端點令牌端點令牌內省端點 等。

OAuth2AuthorizationServerConfigurer 提供以下組態選項

@Bean
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
	OAuth2AuthorizationServerConfigurer authorizationServerConfigurer =
		new OAuth2AuthorizationServerConfigurer();
	http.apply(authorizationServerConfigurer);

	authorizationServerConfigurer
		.registeredClientRepository(registeredClientRepository) (1)
		.authorizationService(authorizationService) (2)
		.authorizationConsentService(authorizationConsentService)   (3)
		.authorizationServerSettings(authorizationServerSettings) (4)
		.tokenGenerator(tokenGenerator) (5)
		.clientAuthentication(clientAuthentication -> { })  (6)
		.authorizationEndpoint(authorizationEndpoint -> { })    (7)
		.deviceAuthorizationEndpoint(deviceAuthorizationEndpoint -> { })    (8)
		.deviceVerificationEndpoint(deviceVerificationEndpoint -> { })  (9)
		.tokenEndpoint(tokenEndpoint -> { })    (10)
		.tokenIntrospectionEndpoint(tokenIntrospectionEndpoint -> { })  (11)
		.tokenRevocationEndpoint(tokenRevocationEndpoint -> { })    (12)
		.authorizationServerMetadataEndpoint(authorizationServerMetadataEndpoint -> { })    (13)
		.oidc(oidc -> oidc
			.providerConfigurationEndpoint(providerConfigurationEndpoint -> { })    (14)
			.logoutEndpoint(logoutEndpoint -> { })  (15)
			.userInfoEndpoint(userInfoEndpoint -> { })  (16)
			.clientRegistrationEndpoint(clientRegistrationEndpoint -> { })  (17)
		);

	return http.build();
}
1 registeredClientRepository():用於管理新的和現有用戶端的 RegisteredClientRepository (REQUIRED)。
2 authorizationService():用於管理新的和現有授權的 OAuth2AuthorizationService
3 authorizationConsentService():用於管理新的和現有授權同意的 OAuth2AuthorizationConsentService
4 authorizationServerSettings():用於自訂 OAuth2 授權伺服器組態設定的 AuthorizationServerSettings (REQUIRED)。
5 tokenGenerator():用於產生 OAuth2 授權伺服器支援的令牌的 OAuth2TokenGenerator
6 clientAuthentication()OAuth2 用戶端驗證的配置器。
7 authorizationEndpoint()OAuth2 授權端點的配置器。
8 deviceAuthorizationEndpoint()OAuth2 裝置授權端點的配置器。
9 deviceVerificationEndpoint()OAuth2 裝置驗證端點的配置器。
10 tokenEndpoint()OAuth2 令牌端點的配置器。
11 tokenIntrospectionEndpoint()OAuth2 令牌內省端點的配置器。
12 tokenRevocationEndpoint()OAuth2 令牌撤銷端點的配置器。
13 authorizationServerMetadataEndpoint()OAuth2 授權伺服器元數據端點的配置器。
14 providerConfigurationEndpoint()OpenID Connect 1.0 提供者組態端點的配置器。
15 logoutEndpoint()OpenID Connect 1.0 登出端點的配置器。
16 userInfoEndpoint()OpenID Connect 1.0 UserInfo 端點的配置器。
17 clientRegistrationEndpoint()OpenID Connect 1.0 用戶端註冊端點的配置器。

配置授權伺服器設定

AuthorizationServerSettings 包含 OAuth2 授權伺服器的組態設定。它指定協定端點的 URI 以及 發行者識別符。協定端點的預設 URI 如下

public final class AuthorizationServerSettings extends AbstractSettings {

	...

	public static Builder builder() {
		return new Builder()
			.authorizationEndpoint("/oauth2/authorize")
			.deviceAuthorizationEndpoint("/oauth2/device_authorization")
			.deviceVerificationEndpoint("/oauth2/device_verification")
			.tokenEndpoint("/oauth2/token")
			.tokenIntrospectionEndpoint("/oauth2/introspect")
			.tokenRevocationEndpoint("/oauth2/revoke")
			.jwkSetEndpoint("/oauth2/jwks")
			.oidcLogoutEndpoint("/connect/logout")
			.oidcUserInfoEndpoint("/userinfo")
			.oidcClientRegistrationEndpoint("/connect/register");
	}

	...

}
AuthorizationServerSettingsREQUIRED(必要) 組件。
@Import(OAuth2AuthorizationServerConfiguration.class) 會自動註冊 AuthorizationServerSettings @Bean,如果尚未提供。

以下範例示範如何自訂組態設定並註冊 AuthorizationServerSettings @Bean

@Bean
public AuthorizationServerSettings authorizationServerSettings() {
	return AuthorizationServerSettings.builder()
		.issuer("https://example.com")
		.authorizationEndpoint("/oauth2/v1/authorize")
		.deviceAuthorizationEndpoint("/oauth2/v1/device_authorization")
		.deviceVerificationEndpoint("/oauth2/v1/device_verification")
		.tokenEndpoint("/oauth2/v1/token")
		.tokenIntrospectionEndpoint("/oauth2/v1/introspect")
		.tokenRevocationEndpoint("/oauth2/v1/revoke")
		.jwkSetEndpoint("/oauth2/v1/jwks")
		.oidcLogoutEndpoint("/connect/v1/logout")
		.oidcUserInfoEndpoint("/connect/v1/userinfo")
		.oidcClientRegistrationEndpoint("/connect/v1/register")
		.build();
}

AuthorizationServerContext 是一個內容物件,其中包含授權伺服器執行階段環境的資訊。它提供對 AuthorizationServerSettings 和「目前」發行者識別符的存取權。

如果未在 AuthorizationServerSettings.builder().issuer(String) 中配置發行者識別符,則會從目前請求解析。
AuthorizationServerContext 可透過 AuthorizationServerContextHolder 存取,後者使用 ThreadLocal 將其與目前請求執行緒關聯。

配置用戶端驗證

OAuth2ClientAuthenticationConfigurer 提供自訂 OAuth2 用戶端驗證的能力。它定義了擴充點,可讓您自訂用戶端驗證請求的預處理、主要處理和後處理邏輯。

OAuth2ClientAuthenticationConfigurer 提供以下組態選項

@Bean
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
	OAuth2AuthorizationServerConfigurer authorizationServerConfigurer =
		new OAuth2AuthorizationServerConfigurer();
	http.apply(authorizationServerConfigurer);

	authorizationServerConfigurer
		.clientAuthentication(clientAuthentication ->
			clientAuthentication
				.authenticationConverter(authenticationConverter)   (1)
				.authenticationConverters(authenticationConvertersConsumer) (2)
				.authenticationProvider(authenticationProvider) (3)
				.authenticationProviders(authenticationProvidersConsumer)   (4)
				.authenticationSuccessHandler(authenticationSuccessHandler) (5)
				.errorResponseHandler(errorResponseHandler) (6)
		);

	return http.build();
}
1 authenticationConverter():新增一個 AuthenticationConverter預處理器),用於嘗試從 HttpServletRequest 中提取用戶端憑證到 OAuth2ClientAuthenticationToken 的實例。
2 authenticationConverters():設定 Consumer,提供對預設和(可選)新增的 AuthenticationConverter List 的存取權,允許新增、移除或自訂特定的 AuthenticationConverter
3 authenticationProvider():新增一個 AuthenticationProvider主要處理器),用於驗證 OAuth2ClientAuthenticationToken
4 authenticationProviders():設定 Consumer,提供對預設和(可選)新增的 AuthenticationProvider List 的存取權,允許新增、移除或自訂特定的 AuthenticationProvider
5 authenticationSuccessHandler()AuthenticationSuccessHandler後處理器),用於處理成功的用戶端驗證,並將 OAuth2ClientAuthenticationTokenSecurityContext 關聯。
6 errorResponseHandler()AuthenticationFailureHandler後處理器),用於處理失敗的用戶端驗證,並傳回 OAuth2Error 回應

OAuth2ClientAuthenticationConfigurer 配置 OAuth2ClientAuthenticationFilter,並將其註冊到 OAuth2 授權伺服器 SecurityFilterChain @BeanOAuth2ClientAuthenticationFilter 是處理用戶端驗證請求的 Filter

預設情況下,OAuth2 令牌端點OAuth2 令牌內省端點OAuth2 令牌撤銷端點 需要用戶端驗證。支援的用戶端驗證方法包括 client_secret_basicclient_secret_postprivate_key_jwtclient_secret_jwttls_client_authself_signed_tls_client_authnone(公開用戶端)。

OAuth2ClientAuthenticationFilter 配置了以下預設值

  • AuthenticationConverter — 由 JwtClientAssertionAuthenticationConverterX509ClientCertificateAuthenticationConverterClientSecretBasicAuthenticationConverterClientSecretPostAuthenticationConverterPublicClientAuthenticationConverter 組成的 DelegatingAuthenticationConverter

  • AuthenticationManager — 由 JwtClientAssertionAuthenticationProviderX509ClientCertificateAuthenticationProviderClientSecretAuthenticationProviderPublicClientAuthenticationProvider 組成的 AuthenticationManager

  • AuthenticationSuccessHandler — 將「已驗證」的 OAuth2ClientAuthenticationToken(目前 Authentication)與 SecurityContext 關聯的內部實作。

  • AuthenticationFailureHandler — 使用與 OAuth2AuthenticationException 關聯的 OAuth2Error 來傳回 OAuth2 錯誤回應的內部實作。

自訂 Jwt 用戶端斷言驗證

JwtClientAssertionDecoderFactory.DEFAULT_JWT_VALIDATOR_FACTORY 是預設工廠,為指定的 RegisteredClient 提供 OAuth2TokenValidator<Jwt>,並用於驗證 Jwt 用戶端斷言的 isssubaudexpnbf 宣告。

JwtClientAssertionDecoderFactory 提供覆寫預設 Jwt 用戶端斷言驗證的能力,方法是提供 Function<RegisteredClient, OAuth2TokenValidator<Jwt>> 類型的自訂工廠給 setJwtValidatorFactory()

JwtClientAssertionDecoderFactoryJwtClientAssertionAuthenticationProvider 使用的預設 JwtDecoderFactory,它為指定的 RegisteredClient 提供 JwtDecoder,並用於在 OAuth2 用戶端驗證期間驗證 Jwt Bearer 令牌。

自訂 JwtClientAssertionDecoderFactory 的常見用例是在 Jwt 用戶端斷言中驗證其他宣告。

以下範例示範如何使用自訂的 JwtClientAssertionDecoderFactory 來配置 JwtClientAssertionAuthenticationProvider,以驗證 Jwt 用戶端斷言中的其他宣告

@Bean
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
	OAuth2AuthorizationServerConfigurer authorizationServerConfigurer =
		new OAuth2AuthorizationServerConfigurer();
	http.apply(authorizationServerConfigurer);

	authorizationServerConfigurer
		.clientAuthentication(clientAuthentication ->
			clientAuthentication
				.authenticationProviders(configureJwtClientAssertionValidator())
		);

	return http.build();
}

private Consumer<List<AuthenticationProvider>> configureJwtClientAssertionValidator() {
	return (authenticationProviders) ->
		authenticationProviders.forEach((authenticationProvider) -> {
			if (authenticationProvider instanceof JwtClientAssertionAuthenticationProvider) {
				// Customize JwtClientAssertionDecoderFactory
				JwtClientAssertionDecoderFactory jwtDecoderFactory = new JwtClientAssertionDecoderFactory();
				Function<RegisteredClient, OAuth2TokenValidator<Jwt>> jwtValidatorFactory = (registeredClient) ->
					new DelegatingOAuth2TokenValidator<>(
						// Use default validators
						JwtClientAssertionDecoderFactory.DEFAULT_JWT_VALIDATOR_FACTORY.apply(registeredClient),
						// Add custom validator
						new JwtClaimValidator<>("claim", "value"::equals));
				jwtDecoderFactory.setJwtValidatorFactory(jwtValidatorFactory);

				((JwtClientAssertionAuthenticationProvider) authenticationProvider)
					.setJwtDecoderFactory(jwtDecoderFactory);
			}
		});
}

自訂 Mutual-TLS 用戶端驗證

當在 OAuth2 用戶端驗證期間使用 ClientAuthenticationMethod.TLS_CLIENT_AUTHClientAuthenticationMethod.SELF_SIGNED_TLS_CLIENT_AUTH 方法時,X509ClientCertificateAuthenticationProvider 用於驗證接收到的用戶端 X509Certificate 鏈。它也與 "Certificate Verifier" 組成,用於在 TLS 握手成功完成後驗證用戶端 X509Certificate 的內容。

PKI Mutual-TLS 方法

對於 PKI Mutual-TLS (ClientAuthenticationMethod.TLS_CLIENT_AUTH) 方法,憑證驗證器的預設實作會根據設定 RegisteredClient.getClientSettings.getX509CertificateSubjectDN() 驗證用戶端 X509Certificate 的主體識別名稱。

如果您需要驗證用戶端 X509Certificate 的另一個屬性,例如,主體替代名稱 (SAN) 項目,則以下範例示範如何使用憑證驗證器的自訂實作來配置 X509ClientCertificateAuthenticationProvider

@Bean
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
	OAuth2AuthorizationServerConfigurer authorizationServerConfigurer =
			new OAuth2AuthorizationServerConfigurer();
	http.apply(authorizationServerConfigurer);

	authorizationServerConfigurer
			.clientAuthentication(clientAuthentication ->
					clientAuthentication
							.authenticationProviders(configureX509ClientCertificateVerifier())
			);

	return http.build();
}

private Consumer<List<AuthenticationProvider>> configureX509ClientCertificateVerifier() {
	return (authenticationProviders) ->
			authenticationProviders.forEach((authenticationProvider) -> {
				if (authenticationProvider instanceof X509ClientCertificateAuthenticationProvider) {
					Consumer<OAuth2ClientAuthenticationContext> certificateVerifier = (clientAuthenticationContext) -> {
						OAuth2ClientAuthenticationToken clientAuthentication = clientAuthenticationContext.getAuthentication();
						RegisteredClient registeredClient = clientAuthenticationContext.getRegisteredClient();
						X509Certificate[] clientCertificateChain = (X509Certificate[]) clientAuthentication.getCredentials();
						X509Certificate clientCertificate = clientCertificateChain[0];

						// TODO Verify Subject Alternative Name (SAN) entry

					};

					((X509ClientCertificateAuthenticationProvider) authenticationProvider)
							.setCertificateVerifier(certificateVerifier);
				}
			});
}

自簽憑證 Mutual-TLS 方法

對於自簽憑證 Mutual-TLS (ClientAuthenticationMethod.SELF_SIGNED_TLS_CLIENT_AUTH) 方法,憑證驗證器的預設實作將使用設定 RegisteredClient.getClientSettings.getJwkSetUrl() 檢索用戶端的 JSON Web Key 集合,並期望找到與在 TLS 握手期間接收到的用戶端 X509Certificate 的匹配項。

RegisteredClient.getClientSettings.getJwkSetUrl() 設定用於透過 JSON Web Key (JWK) 集合檢索用戶端的憑證。憑證以集合中個別 JWK 的 x5c 參數表示。

用戶端憑證繫結的存取令牌

當在令牌端點使用 Mutual-TLS 用戶端驗證時,授權伺服器能夠將發行的存取令牌繫結到用戶端的 X509Certificate。繫結是透過計算用戶端 X509Certificate 的 SHA-256 指紋,並將指紋與存取令牌關聯來完成。例如,JWT 存取令牌將在頂層 cnf(確認方法)宣告中包含 x5t#S256 宣告,其中包含 X509Certificate 指紋。

將存取令牌繫結到用戶端的 X509Certificate 提供了在受保護資源存取期間實作持有證明機制的可能性。例如,受保護資源將取得在 Mutual-TLS 驗證期間使用的用戶端 X509Certificate,然後驗證憑證指紋是否與和存取令牌關聯的 x5t#S256 宣告匹配。

以下範例示範如何為用戶端啟用憑證繫結的存取令牌

RegisteredClient mtlsClient = RegisteredClient.withId(UUID.randomUUID().toString())
		.clientId("mtls-client")
		.clientAuthenticationMethod(ClientAuthenticationMethod.TLS_CLIENT_AUTH)
		.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
		.scope("scope-a")
		.clientSettings(
				ClientSettings.builder()
						.x509CertificateSubjectDN("CN=mtls-client,OU=Spring Samples,O=Spring,C=US")
						.build()
		)
		.tokenSettings(
				TokenSettings.builder()
						.x509CertificateBoundAccessTokens(true)
						.build()
		)
		.build();