核心模型 / 組件

RegisteredClient

RegisteredClient 是在授權伺服器上註冊的用戶端表示法。用戶端必須先在授權伺服器上註冊,才能啟動授權 Grant Flow,例如 authorization_codeclient_credentials

在用戶端註冊期間,會為用戶端分配一個唯一的用戶端識別符,(可選)用戶端密碼(取決於用戶端類型),以及與其唯一用戶端識別符相關聯的元數據。用戶端的元數據範圍可以從面向人類的顯示字串(例如用戶端名稱)到特定於協定流程的項目(例如有效重新導向 URI 的列表)。

Spring Security 的 OAuth2 Client 支援中對應的用戶端註冊模型是 ClientRegistration

用戶端的主要目的是請求存取受保護的資源。用戶端首先通過向授權伺服器驗證身份並提交授權 Grant 來請求存取令牌。授權伺服器驗證用戶端和授權 Grant,如果它們有效,則發放存取令牌。用戶端現在可以通過提交存取令牌向資源伺服器請求受保護的資源。

以下範例示範如何配置允許執行 authorization_code Grant Flow 以請求存取令牌的 RegisteredClient

RegisteredClient registeredClient = RegisteredClient.withId(UUID.randomUUID().toString())
	.clientId("client-a")
	.clientSecret("{noop}secret")   (1)
	.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
	.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
	.redirectUri("http://127.0.0.1:8080/authorized")
	.scope("scope-a")
	.clientSettings(ClientSettings.builder().requireAuthorizationConsent(true).build())
	.build();
1 {noop} 代表 Spring Security NoOpPasswordEncoderPasswordEncoder ID。

Spring Security OAuth2 Client 支援中的對應組態為

spring:
  security:
    oauth2:
      client:
        registration:
          client-a:
            provider: spring
            client-id: client-a
            client-secret: secret
            authorization-grant-type: authorization_code
            redirect-uri: "http://127.0.0.1:8080/authorized"
            scope: scope-a
        provider:
          spring:
            issuer-uri: https://127.0.0.1:9000

RegisteredClient 具有與其唯一用戶端識別符相關聯的元數據(屬性),並定義如下

public class RegisteredClient implements Serializable {
	private String id;  (1)
	private String clientId;    (2)
	private Instant clientIdIssuedAt;   (3)
	private String clientSecret;    (4)
	private Instant clientSecretExpiresAt;  (5)
	private String clientName;  (6)
	private Set<ClientAuthenticationMethod> clientAuthenticationMethods;    (7)
	private Set<AuthorizationGrantType> authorizationGrantTypes;    (8)
	private Set<String> redirectUris;   (9)
	private Set<String> postLogoutRedirectUris; (10)
	private Set<String> scopes; (11)
	private ClientSettings clientSettings;  (12)
	private TokenSettings tokenSettings;    (13)

	...

}
1 id:唯一識別 RegisteredClient 的 ID。
2 clientId:用戶端識別符。
3 clientIdIssuedAt:發行用戶端識別符的時間。
4 clientSecret:用戶端的密碼。該值應使用 Spring Security 的 PasswordEncoder 進行編碼。
5 clientSecretExpiresAt:用戶端密碼過期的時間。
6 clientName:用於用戶端的描述性名稱。該名稱可能在某些情況下使用,例如在同意頁面中顯示用戶端名稱時。
7 clientAuthenticationMethods:用戶端可能使用的身份驗證方法。支援的值為 client_secret_basicclient_secret_postprivate_key_jwtclient_secret_jwtnone (公共用戶端)
8 authorizationGrantTypes:用戶端可以使用的授權 Grant Type。支援的值為 authorization_codeclient_credentialsrefresh_tokenurn:ietf:params:oauth:grant-type:device_codeurn:ietf:params:oauth:grant-type:token-exchange
9 redirectUris:註冊的 重新導向 URI,用戶端可以在基於重新導向的流程中使用它們 - 例如,authorization_code Grant。
10 postLogoutRedirectUris:用戶端可以用於登出的登出後重新導向 URI。
11 scopes:允許用戶端請求的 scope。
12 clientSettings:用戶端的自訂設定 - 例如,需要 PKCE、需要授權同意等等。
13 tokenSettings:發放給用戶端的 OAuth2 令牌的自訂設定 - 例如,存取/刷新令牌的存活時間、重複使用刷新令牌等等。

RegisteredClientRepository

RegisteredClientRepository 是中心組件,可在其中註冊新用戶端並查詢現有用戶端。其他組件在遵循特定協定流程時使用它,例如用戶端身份驗證、授權 Grant 處理、令牌內省、動態用戶端註冊等等。

提供的 RegisteredClientRepository 實作是 InMemoryRegisteredClientRepositoryJdbcRegisteredClientRepositoryInMemoryRegisteredClientRepository 實作將 RegisteredClient 實例儲存在記憶體中,並且建議在開發和測試期間使用。JdbcRegisteredClientRepository 是 JDBC 實作,它通過使用 JdbcOperations 持久化 RegisteredClient 實例。

RegisteredClientRepository必要組件。

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

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

或者,您可以通過 OAuth2AuthorizationServerConfigurer 配置 RegisteredClientRepository

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

	authorizationServerConfigurer
		.registeredClientRepository(registeredClientRepository);

	...

	return http.build();
}
當同時應用多個組態選項時,OAuth2AuthorizationServerConfigurer 非常有用。

OAuth2Authorization

OAuth2Authorization 是 OAuth2 授權的表示法,它保存與資源擁有者或自身(在 client_credentials 授權 Grant Type 的情況下)授予用戶端的授權相關的狀態。

Spring Security 的 OAuth2 Client 支援中對應的授權模型是 OAuth2AuthorizedClient

在成功完成授權 Grant Flow 後,將創建 OAuth2Authorization 並將 OAuth2AccessToken、一個(可選)OAuth2RefreshToken 以及特定於已執行授權 Grant Type 的其他狀態相關聯。

OAuth2Authorization 相關聯的 OAuth2Token 實例因授權 Grant Type 而異。

對於 OAuth2 authorization_code Grant,關聯了 OAuth2AuthorizationCodeOAuth2AccessToken 和(可選)OAuth2RefreshToken

對於 OpenID Connect 1.0 authorization_code Grant,關聯了 OAuth2AuthorizationCodeOidcIdTokenOAuth2AccessToken 和(可選)OAuth2RefreshToken

對於 OAuth2 client_credentials Grant,僅關聯了 OAuth2AccessToken

OAuth2Authorization 及其屬性定義如下

public class OAuth2Authorization implements Serializable {
	private String id;  (1)
	private String registeredClientId;  (2)
	private String principalName;   (3)
	private AuthorizationGrantType authorizationGrantType;  (4)
	private Set<String> authorizedScopes;   (5)
	private Map<Class<? extends OAuth2Token>, Token<?>> tokens; (6)
	private Map<String, Object> attributes; (7)

	...

}
1 id:唯一識別 OAuth2Authorization 的 ID。
2 registeredClientId:唯一識別 RegisteredClient 的 ID。
3 principalName:資源擁有者(或用戶端)的主體名稱。
4 authorizationGrantType:使用的 AuthorizationGrantType
5 authorizedScopes:為用戶端授權的 scope 的 Set
6 tokens:特定於已執行授權 Grant Type 的 OAuth2Token 實例(和相關元數據)。
7 attributes:特定於已執行授權 Grant Type 的其他屬性 - 例如,已驗證的 PrincipalOAuth2AuthorizationRequest 等等。

OAuth2Authorization 及其關聯的 OAuth2Token 實例具有設定的生命週期。新發行的 OAuth2Token 是活動的,當它過期或失效(撤銷)時,它會變為非活動狀態。當所有關聯的 OAuth2Token 實例都處於非活動狀態時,OAuth2Authorization(隱含地)為非活動狀態。每個 OAuth2Token 都保存在 OAuth2Authorization.Token 中,它提供用於 isExpired()isInvalidated()isActive() 的存取器。

OAuth2Authorization.Token 還提供 getClaims(),它返回與 OAuth2Token 關聯的宣告(如果有)。

OAuth2AuthorizationService

OAuth2AuthorizationService 是中心組件,可在其中儲存新的授權並查詢現有的授權。其他組件在遵循特定協定流程時使用它 - 例如,用戶端身份驗證、授權 Grant 處理、令牌內省、令牌撤銷、動態用戶端註冊等等。

提供的 OAuth2AuthorizationService 實作是 InMemoryOAuth2AuthorizationServiceJdbcOAuth2AuthorizationServiceInMemoryOAuth2AuthorizationService 實作將 OAuth2Authorization 實例儲存在記憶體中,並且建議在開發和測試期間使用。JdbcOAuth2AuthorizationService 是 JDBC 實作,它通過使用 JdbcOperations 持久化 OAuth2Authorization 實例。

OAuth2AuthorizationService 是一個可選組件,預設為 InMemoryOAuth2AuthorizationService

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

@Bean
public OAuth2AuthorizationService authorizationService() {
	return new InMemoryOAuth2AuthorizationService();
}

或者,您可以通過 OAuth2AuthorizationServerConfigurer 配置 OAuth2AuthorizationService

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

	authorizationServerConfigurer
		.authorizationService(authorizationService);

	...

	return http.build();
}
當同時應用多個組態選項時,OAuth2AuthorizationServerConfigurer 非常有用。

OAuth2AuthorizationConsent 是來自 OAuth2 授權請求流程的授權「同意」(決策)的表示法 - 例如,authorization_code Grant,它保存資源擁有者授予用戶端的權限。

在授權存取用戶端時,資源擁有者可能僅授予用戶端請求的權限的子集。典型的用例是 authorization_code Grant Flow,其中用戶端請求 scope,資源擁有者授予(或拒絕)對請求的 scope 的存取權。

在完成 OAuth2 授權請求流程後,將創建(或更新)OAuth2AuthorizationConsent,並將授予的權限與用戶端和資源擁有者關聯。

OAuth2AuthorizationConsent 及其屬性定義如下

public final class OAuth2AuthorizationConsent implements Serializable {
	private final String registeredClientId;    (1)
	private final String principalName; (2)
	private final Set<GrantedAuthority> authorities;    (3)

	...

}
1 registeredClientId:唯一識別 RegisteredClient 的 ID。
2 principalName:資源擁有者的主體名稱。
3 authorities:資源擁有者授予用戶端的權限。權限可以表示 scope、宣告、權限、角色等等。

OAuth2AuthorizationConsentService 是中心組件,可在其中儲存新的授權同意並查詢現有的授權同意。實作 OAuth2 授權請求流程的組件主要使用它 - 例如,authorization_code Grant。

提供的 OAuth2AuthorizationConsentService 實作是 InMemoryOAuth2AuthorizationConsentServiceJdbcOAuth2AuthorizationConsentServiceInMemoryOAuth2AuthorizationConsentService 實作將 OAuth2AuthorizationConsent 實例儲存在記憶體中,並且建議用於開發和測試。JdbcOAuth2AuthorizationConsentService 是 JDBC 實作,它通過使用 JdbcOperations 持久化 OAuth2AuthorizationConsent 實例。

OAuth2AuthorizationConsentService 是一個可選組件,預設為 InMemoryOAuth2AuthorizationConsentService

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

@Bean
public OAuth2AuthorizationConsentService authorizationConsentService() {
	return new InMemoryOAuth2AuthorizationConsentService();
}

或者,您可以通過 OAuth2AuthorizationServerConfigurer 配置 OAuth2AuthorizationConsentService

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

	authorizationServerConfigurer
		.authorizationConsentService(authorizationConsentService);

	...

	return http.build();
}
當同時應用多個組態選項時,OAuth2AuthorizationServerConfigurer 非常有用。

OAuth2TokenContext

OAuth2TokenContext 是一個上下文物件,它保存與 OAuth2Token 相關聯的資訊,並由 OAuth2TokenGeneratorOAuth2TokenCustomizer 使用。

OAuth2TokenContext 提供以下存取器

public interface OAuth2TokenContext extends Context {

	default RegisteredClient getRegisteredClient() ...  (1)

	default <T extends Authentication> T getPrincipal() ... (2)

	default AuthorizationServerContext getAuthorizationServerContext() ...    (3)

	@Nullable
	default OAuth2Authorization getAuthorization() ...  (4)

	default Set<String> getAuthorizedScopes() ...   (5)

	default OAuth2TokenType getTokenType() ...  (6)

	default AuthorizationGrantType getAuthorizationGrantType() ...  (7)

	default <T extends Authentication> T getAuthorizationGrant() ...    (8)

	...

}
1 getRegisteredClient():與授權 Grant 相關聯的 RegisteredClient
2 getPrincipal():資源擁有者(或用戶端)的 Authentication 實例。
3 getAuthorizationServerContext():保存授權伺服器運行時環境資訊的 AuthorizationServerContext 物件。
4 getAuthorization():與授權 Grant 相關聯的 OAuth2Authorization
5 getAuthorizedScopes():為用戶端授權的 scope。
6 getTokenType():要生成的 OAuth2TokenType。支援的值為 codeaccess_tokenrefresh_tokenid_token
7 getAuthorizationGrantType():與授權 Grant 相關聯的 AuthorizationGrantType
8 getAuthorizationGrant():由處理授權 Grant 的 AuthenticationProvider 使用的 Authentication 實例。

OAuth2TokenGenerator

OAuth2TokenGenerator 負責從提供的 OAuth2TokenContext 中包含的資訊生成 OAuth2Token

生成的 OAuth2Token 主要取決於 OAuth2TokenContext 中指定的 OAuth2TokenType 的類型。

例如,當 OAuth2TokenTypevalue

  • code,則生成 OAuth2AuthorizationCode

  • access_token,則生成 OAuth2AccessToken

  • refresh_token,則生成 OAuth2RefreshToken

  • id_token,則生成 OidcIdToken

此外,生成的 OAuth2AccessToken 的格式因 RegisteredClient 配置的 TokenSettings.getAccessTokenFormat() 而異。如果格式為 OAuth2TokenFormat.SELF_CONTAINED(預設值),則生成 Jwt。如果格式為 OAuth2TokenFormat.REFERENCE,則生成「不透明」令牌。

最後,如果生成的 OAuth2Token 具有一組宣告並實作 ClaimAccessor,則可以從 OAuth2Authorization.Token.getClaims() 存取宣告。

OAuth2TokenGenerator 主要由實作授權 Grant 處理的組件使用 - 例如,authorization_codeclient_credentialsrefresh_token

提供的實作是 OAuth2AccessTokenGeneratorOAuth2RefreshTokenGeneratorJwtGeneratorOAuth2AccessTokenGenerator 生成「不透明」(OAuth2TokenFormat.REFERENCE)存取令牌,而 JwtGenerator 生成 JwtOAuth2TokenFormat.SELF_CONTAINED)。

OAuth2TokenGenerator 是一個可選組件,預設為由 OAuth2AccessTokenGeneratorOAuth2RefreshTokenGenerator 組成的 DelegatingOAuth2TokenGenerator
如果註冊了 JwtEncoder @BeanJWKSource<SecurityContext> @Bean,則 JwtGenerator 也會額外組成在 DelegatingOAuth2TokenGenerator 中。

OAuth2TokenGenerator 提供了極大的靈活性,因為它可以支援 access_tokenrefresh_token 的任何自訂令牌格式。

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

@Bean
public OAuth2TokenGenerator<?> tokenGenerator() {
	JwtEncoder jwtEncoder = ...
	JwtGenerator jwtGenerator = new JwtGenerator(jwtEncoder);
	OAuth2AccessTokenGenerator accessTokenGenerator = new OAuth2AccessTokenGenerator();
	OAuth2RefreshTokenGenerator refreshTokenGenerator = new OAuth2RefreshTokenGenerator();
	return new DelegatingOAuth2TokenGenerator(
			jwtGenerator, accessTokenGenerator, refreshTokenGenerator);
}

或者,您可以通過 OAuth2AuthorizationServerConfigurer 配置 OAuth2TokenGenerator

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

	authorizationServerConfigurer
		.tokenGenerator(tokenGenerator);

	...

	return http.build();
}
當同時應用多個組態選項時,OAuth2AuthorizationServerConfigurer 非常有用。

OAuth2TokenCustomizer

OAuth2TokenCustomizer 提供了自訂 OAuth2Token 屬性的能力,這些屬性可在提供的 OAuth2TokenContext 中存取。它由 OAuth2TokenGenerator 使用,以使其能夠在生成 OAuth2Token 之前自訂其屬性。

使用 OAuth2TokenClaimsContextimplements OAuth2TokenContext)的泛型類型宣告的 OAuth2TokenCustomizer<OAuth2TokenClaimsContext> 提供了自訂「不透明」OAuth2AccessToken 的宣告的能力。OAuth2TokenClaimsContext.getClaims() 提供了對 OAuth2TokenClaimsSet.Builder 的存取,允許新增、替換和移除宣告。

以下範例示範如何實作 OAuth2TokenCustomizer<OAuth2TokenClaimsContext> 並使用 OAuth2AccessTokenGenerator 配置它

@Bean
public OAuth2TokenGenerator<?> tokenGenerator() {
	JwtEncoder jwtEncoder = ...
	JwtGenerator jwtGenerator = new JwtGenerator(jwtEncoder);
	OAuth2AccessTokenGenerator accessTokenGenerator = new OAuth2AccessTokenGenerator();
	accessTokenGenerator.setAccessTokenCustomizer(accessTokenCustomizer());
	OAuth2RefreshTokenGenerator refreshTokenGenerator = new OAuth2RefreshTokenGenerator();
	return new DelegatingOAuth2TokenGenerator(
			jwtGenerator, accessTokenGenerator, refreshTokenGenerator);
}

@Bean
public OAuth2TokenCustomizer<OAuth2TokenClaimsContext> accessTokenCustomizer() {
	return context -> {
		OAuth2TokenClaimsSet.Builder claims = context.getClaims();
		// Customize claims

	};
}
如果未將 OAuth2TokenGenerator 作為 @Bean 提供或未通過 OAuth2AuthorizationServerConfigurer 配置,則 OAuth2TokenCustomizer<OAuth2TokenClaimsContext> @Bean 將自動使用 OAuth2AccessTokenGenerator 進行配置。

使用 JwtEncodingContextimplements OAuth2TokenContext)的泛型類型宣告的 OAuth2TokenCustomizer<JwtEncodingContext> 提供了自訂 Jwt 的標頭和宣告的能力。JwtEncodingContext.getJwsHeader() 提供了對 JwsHeader.Builder 的存取,允許新增、替換和移除標頭。JwtEncodingContext.getClaims() 提供了對 JwtClaimsSet.Builder 的存取,允許新增、替換和移除宣告。

以下範例示範如何實作 OAuth2TokenCustomizer<JwtEncodingContext> 並使用 JwtGenerator 配置它

@Bean
public OAuth2TokenGenerator<?> tokenGenerator() {
	JwtEncoder jwtEncoder = ...
	JwtGenerator jwtGenerator = new JwtGenerator(jwtEncoder);
	jwtGenerator.setJwtCustomizer(jwtCustomizer());
	OAuth2AccessTokenGenerator accessTokenGenerator = new OAuth2AccessTokenGenerator();
	OAuth2RefreshTokenGenerator refreshTokenGenerator = new OAuth2RefreshTokenGenerator();
	return new DelegatingOAuth2TokenGenerator(
			jwtGenerator, accessTokenGenerator, refreshTokenGenerator);
}

@Bean
public OAuth2TokenCustomizer<JwtEncodingContext> jwtCustomizer() {
	return context -> {
		JwsHeader.Builder headers = context.getJwsHeader();
		JwtClaimsSet.Builder claims = context.getClaims();
		if (context.getTokenType().equals(OAuth2TokenType.ACCESS_TOKEN)) {
			// Customize headers/claims for access_token

		} else if (context.getTokenType().getValue().equals(OidcParameterNames.ID_TOKEN)) {
			// Customize headers/claims for id_token

		}
	};
}
如果未將 OAuth2TokenGenerator 作為 @Bean 提供或未通過 OAuth2AuthorizationServerConfigurer 配置,則 OAuth2TokenCustomizer<JwtEncodingContext> @Bean 將自動使用 JwtGenerator 進行配置。

SessionRegistry

如果啟用 OpenID Connect 1.0,則會使用 SessionRegistry 實例來追蹤已驗證的會話。SessionRegistry 由與 OAuth2 授權端點關聯的 SessionAuthenticationStrategy 的預設實作使用,以註冊新的已驗證會話。

如果未註冊 SessionRegistry @Bean,則將使用預設實作 SessionRegistryImpl
如果註冊了 SessionRegistry @Bean 並且是 SessionRegistryImpl 的實例,則也註冊 HttpSessionEventPublisher @Bean,因為它負責通知 SessionRegistryImpl 會話生命週期事件,例如 SessionDestroyedEvent,以提供移除 SessionInformation 實例的能力。

當最終用戶請求登出時,OpenID Connect 1.0 登出端點 使用 SessionRegistry 查找與已驗證的最終用戶關聯的 SessionInformation 以執行登出。

如果正在使用 Spring Security 的 並行會話控制 功能,則建議註冊 SessionRegistry @Bean 以確保它在 Spring Security 的並行會話控制和 Spring Authorization Server 的登出功能之間共享。

以下範例示範如何註冊 SessionRegistry @BeanHttpSessionEventPublisher @BeanSessionRegistryImpl 需要)

@Bean
public SessionRegistry sessionRegistry() {
	return new SessionRegistryImpl();
}

@Bean
public HttpSessionEventPublisher httpSessionEventPublisher() {
	return new HttpSessionEventPublisher();
}