核心模型/元件
RegisteredClient
RegisteredClient 是一個已註冊到授權伺服器的客戶端的表示。客戶端必須在授權伺服器註冊後才能發起授權許可流程,例如 authorization_code 或 client_credentials。
在客戶端註冊期間,客戶端被分配一個唯一的客戶端識別符號,(可選)一個客戶端金鑰(取決於客戶端型別),以及與其唯一客戶端識別符號關聯的元資料。客戶端的元資料可以從面向人類的顯示字串(例如客戶端名稱)到特定於協議流的項(例如有效重定向 URI 列表)。
| Spring Security 的 OAuth2 客戶端支援中對應的客戶端註冊模型是ClientRegistration。 |
客戶端的主要目的是請求訪問受保護的資源。客戶端首先透過向授權伺服器進行身份驗證並提交授權許可來請求訪問令牌。授權伺服器對客戶端和授權許可進行身份驗證,如果它們有效,則頒發訪問令牌。客戶端現在可以透過提交訪問令牌來請求資源伺服器中的受保護資源。
以下示例展示瞭如何配置一個 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 的NoOpPasswordEncoder 的 PasswordEncoder ID。 |
Spring Security 的OAuth2 客戶端支援中對應的配置是
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://: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_basic、client_secret_post、private_key_jwt、client_secret_jwt 和 none (公共客戶端)。 |
| 8 | authorizationGrantTypes: 客戶端可以使用的授權許可型別。支援的值有 authorization_code、client_credentials、refresh_token、urn:ietf:params:oauth:grant-type:device_code 和 urn:ietf:params:oauth:grant-type:token-exchange。 |
| 9 | redirectUris: 客戶端在基於重定向的流程中可能使用的已註冊重定向 URI,例如 authorization_code 許可。 |
| 10 | postLogoutRedirectUris: 客戶端可能用於登出的登出後重定向 URI。 |
| 11 | scopes: 客戶端被允許請求的範圍。 |
| 12 | clientSettings: 客戶端的自定義設定——例如,要求PKCE、要求授權同意等。 |
| 13 | tokenSettings: 頒發給客戶端的 OAuth2 令牌的自定義設定——例如,訪問/重新整理令牌的生存時間、重複使用重新整理令牌等。 |
RegisteredClientRepository
RegisteredClientRepository 是核心元件,用於註冊新客戶端和查詢現有客戶端。它被其他元件用於遵循特定協議流程時,例如客戶端身份驗證、授權許可處理、令牌自省、動態客戶端註冊等。
RegisteredClientRepository 的提供實現包括 InMemoryRegisteredClientRepository 和 JdbcRegisteredClientRepository。InMemoryRegisteredClientRepository 實現將 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 =
OAuth2AuthorizationServerConfigurer.authorizationServer();
http
.securityMatcher(authorizationServerConfigurer.getEndpointsMatcher())
.with(authorizationServerConfigurer, (authorizationServer) ->
authorizationServer
.registeredClientRepository(registeredClientRepository)
)
...
return http.build();
}
當同時應用多個配置選項時,OAuth2AuthorizationServerConfigurer 會很有用。 |
OAuth2Authorization
OAuth2Authorization 是 OAuth2 授權的表示,它儲存了授予客戶端的授權相關狀態,該授權由資源所有者授予,或者在 client_credentials 授權許可型別的情況下由客戶端本身授予。
| Spring Security 的 OAuth2 客戶端支援中對應的授權模型是OAuth2AuthorizedClient。 |
授權許可流程成功完成後,將建立 OAuth2Authorization 並關聯一個OAuth2AccessToken、(可選)一個OAuth2RefreshToken,以及特定於所執行的授權許可型別的附加狀態。
與 OAuth2Authorization 關聯的 OAuth2Token 例項因授權許可型別而異。
對於 OAuth2 授權碼許可,關聯 OAuth2AuthorizationCode、OAuth2AccessToken 和(可選)OAuth2RefreshToken。
對於 OpenID Connect 1.0 授權碼許可,關聯 OAuth2AuthorizationCode、OidcIdToken、OAuth2AccessToken 和(可選)OAuth2RefreshToken。
對於 OAuth2 客戶端憑證許可,僅關聯 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: 為客戶端授權的範圍 Set。 |
| 6 | tokens: 特定於所執行授權許可型別的 OAuth2Token 例項(及關聯元資料)。 |
| 7 | attributes: 特定於所執行授權許可型別的附加屬性——例如,已認證的 Principal、OAuth2AuthorizationRequest 等。 |
OAuth2Authorization 及其關聯的 OAuth2Token 例項具有設定的生命週期。新頒發的 OAuth2Token 處於活動狀態,並在過期或失效(撤銷)時變為非活動狀態。當所有關聯的 OAuth2Token 例項都處於非活動狀態時,OAuth2Authorization(隱式)處於非活動狀態。每個 OAuth2Token 都儲存在 OAuth2Authorization.Token 中,後者提供了 isExpired()、isInvalidated() 和 isActive() 的訪問器。
OAuth2Authorization.Token 還提供 getClaims(),它返回與 OAuth2Token 關聯的宣告(如果有)。
OAuth2AuthorizationService
OAuth2AuthorizationService 是核心元件,用於儲存新授權和查詢現有授權。它被其他元件用於遵循特定協議流程時——例如,客戶端身份驗證、授權許可處理、令牌自省、令牌撤銷、動態客戶端註冊等。
OAuth2AuthorizationService 的提供實現包括 InMemoryOAuth2AuthorizationService 和 JdbcOAuth2AuthorizationService。InMemoryOAuth2AuthorizationService 實現將 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 =
OAuth2AuthorizationServerConfigurer.authorizationServer();
http
.securityMatcher(authorizationServerConfigurer.getEndpointsMatcher())
.with(authorizationServerConfigurer, (authorizationServer) ->
authorizationServer
.authorizationService(authorizationService)
)
...
return http.build();
}
當同時應用多個配置選項時,OAuth2AuthorizationServerConfigurer 會很有用。 |
OAuth2AuthorizationConsent
OAuth2AuthorizationConsent 是 OAuth2 授權請求流程中的授權“同意”(決策)的表示——例如,authorization_code 許可,它儲存了資源所有者授予客戶端的許可權。
在授權客戶端訪問時,資源所有者可能只授予客戶端請求的許可權的一個子集。典型的用例是 authorization_code 許可流程,其中客戶端請求範圍,資源所有者授予(或拒絕)對請求範圍的訪問。
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: 資源所有者授予客戶端的許可權。許可權可以表示範圍、宣告、許可、角色等。 |
OAuth2AuthorizationConsentService
OAuth2AuthorizationConsentService 是核心元件,用於儲存新的授權同意和查詢現有授權同意。它主要由實現 OAuth2 授權請求流程的元件使用——例如,authorization_code 許可。
OAuth2AuthorizationConsentService 的提供實現包括 InMemoryOAuth2AuthorizationConsentService 和 JdbcOAuth2AuthorizationConsentService。InMemoryOAuth2AuthorizationConsentService 實現將 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 =
OAuth2AuthorizationServerConfigurer.authorizationServer();
http
.securityMatcher(authorizationServerConfigurer.getEndpointsMatcher())
.with(authorizationServerConfigurer, (authorizationServer) ->
authorizationServer
.authorizationConsentService(authorizationConsentService)
)
...
return http.build();
}
當同時應用多個配置選項時,OAuth2AuthorizationServerConfigurer 會很有用。 |
OAuth2TokenContext
OAuth2TokenContext 是一個上下文物件,它儲存與 OAuth2Token 相關的資訊,並由 OAuth2TokenGenerator 和 OAuth2TokenCustomizer 使用。
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(): 與授權許可關聯的 RegisteredClient。 |
| 2 | getPrincipal(): 資源所有者(或客戶端)的 Authentication 例項。 |
| 3 | getAuthorizationServerContext(): 儲存授權伺服器執行時環境資訊的 AuthorizationServerContext 物件。 |
| 4 | getAuthorization(): 與授權許可關聯的 OAuth2Authorization。 |
| 5 | getAuthorizedScopes(): 為客戶端授權的範圍。 |
| 6 | getTokenType(): 要生成的 OAuth2TokenType。支援的值有 code、access_token、refresh_token 和 id_token。 |
| 7 | getAuthorizationGrantType(): 與授權許可關聯的 AuthorizationGrantType。 |
| 8 | getAuthorizationGrant(): AuthenticationProvider 用於處理授權許可的 Authentication 例項。 |
OAuth2TokenGenerator
OAuth2TokenGenerator 負責根據提供的 OAuth2TokenContext 中包含的資訊生成 OAuth2Token。
生成的 OAuth2Token 主要取決於 OAuth2TokenContext 中指定的 OAuth2TokenType 型別。
例如,當 OAuth2TokenType 的 value 是
-
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 主要用於實現授權許可處理的元件——例如,authorization_code、client_credentials 和 refresh_token。
提供的實現有 OAuth2AccessTokenGenerator、OAuth2RefreshTokenGenerator 和 JwtGenerator。OAuth2AccessTokenGenerator 生成“不透明”(OAuth2TokenFormat.REFERENCE)訪問令牌,而 JwtGenerator 生成 Jwt(OAuth2TokenFormat.SELF_CONTAINED)。
OAuth2TokenGenerator 是一個可選元件,預設由 OAuth2AccessTokenGenerator 和 OAuth2RefreshTokenGenerator 組成的 DelegatingOAuth2TokenGenerator。 |
如果註冊了 JwtEncoder @Bean 或 JWKSource<SecurityContext> @Bean,則 DelegatingOAuth2TokenGenerator 中還會額外組合一個 JwtGenerator。 |
OAuth2TokenGenerator 提供了極大的靈活性,因為它支援任何自定義的 access_token 和 refresh_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 =
OAuth2AuthorizationServerConfigurer.authorizationServer();
http
.securityMatcher(authorizationServerConfigurer.getEndpointsMatcher())
.with(authorizationServerConfigurer, (authorizationServer) ->
authorizationServer
.tokenGenerator(tokenGenerator)
)
...
return http.build();
}
當同時應用多個配置選項時,OAuth2AuthorizationServerConfigurer 會很有用。 |
OAuth2TokenCustomizer
OAuth2TokenCustomizer 提供了自定義 OAuth2Token 屬性的能力,這些屬性可在提供的 OAuth2TokenContext 中訪問。它由 OAuth2TokenGenerator 使用,以便在生成 OAuth2Token 之前自定義其屬性。
宣告為泛型型別 OAuth2TokenClaimsContext(implements 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 中。 |
宣告為泛型型別 JwtEncodingContext(implements 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 中。 |
| 有關如何自定義 ID 令牌的示例,請參閱指南操作指南:自定義 OpenID Connect 1.0 使用者資訊響應。 |
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 授權伺服器的登出功能之間共享。
以下示例展示瞭如何註冊 SessionRegistry @Bean 和 HttpSessionEventPublisher @Bean(SessionRegistryImpl 所需)
@Bean
public SessionRegistry sessionRegistry() {
return new SessionRegistryImpl();
}
@Bean
public HttpSessionEventPublisher httpSessionEventPublisher() {
return new HttpSessionEventPublisher();
}