核心模型 / 元件
RegisteredClient
RegisteredClient
是授權伺服器上註冊的客戶端表示。客戶端必須先在授權伺服器上註冊,然後才能啟動授權許可流程,例如 authorization_code
或 client_credentials
。
在客戶端註冊期間,客戶端會被分配一個唯一的客戶端識別符號,一個(可選的)客戶端金鑰(取決於客戶端型別),以及與其唯一客戶端識別符號相關的元資料。客戶端的元資料範圍很廣,從面向人類的顯示字串(如客戶端名稱)到特定於協議流程的專案(如有效重定向 URI 列表)。
Spring Security OAuth2 Client 支援中相應的客戶端註冊模型是 ClientRegistration 。 |
客戶端的主要目的是請求訪問受保護資源。客戶端首先透過向授權伺服器認證並出示授權許可來請求訪問令牌。授權伺服器會認證客戶端和授權許可,如果它們有效,則頒發訪問令牌。客戶端現在可以通過出示訪問令牌向資源伺服器請求受保護資源。
以下示例展示瞭如何配置一個允許執行 authorization_code
許可流程以請求訪問令牌的 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 PasswordEncoder 的 NoOpPasswordEncoder 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://: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 : 客戶端在基於重定向的流程(例如 authorization_code 許可)中可以使用的已註冊重定向 URI。 |
10 | postLogoutRedirectUris : 客戶端在登出後可以使用的重定向 URI。 |
11 | scopes : 客戶端被允許請求的範圍 (scope)。 |
12 | clientSettings : 客戶端的自定義設定,例如要求 PKCE、要求授權同意等。 |
13 | tokenSettings : 頒發給客戶端的 OAuth2 令牌的自定義設定,例如訪問/重新整理令牌的生命週期、是否重用重新整理令牌等。 |
RegisteredClientRepository
RegisteredClientRepository
是用於註冊新客戶端和查詢現有客戶端的核心元件。其他元件在執行特定協議流程時(如客戶端認證、授權許可處理、令牌內省、動態客戶端註冊等)會使用它。
提供了 InMemoryRegisteredClientRepository
和 JdbcRegisteredClientRepository
兩種 RegisteredClientRepository
實現。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 Client 支援中相應的授權模型是 OAuth2AuthorizedClient 。 |
授權許可流程成功完成後,會建立一個 OAuth2Authorization
,並關聯一個 OAuth2AccessToken
、一個(可選的)OAuth2RefreshToken
,以及特定於已執行授權許可型別的附加狀態。
與 OAuth2Authorization
關聯的 OAuth2Token
例項會根據授權許可型別的不同而變化。
對於 OAuth2 authorization_code
許可,會關聯一個 OAuth2AuthorizationCode
、一個 OAuth2AccessToken
和一個(可選的)OAuth2RefreshToken
。
對於 OpenID Connect 1.0 authorization_code
許可,會關聯一個 OAuth2AuthorizationCode
、一個 OidcIdToken
、一個 OAuth2AccessToken
和一個(可選的)OAuth2RefreshToken
。
對於 OAuth2 client_credentials
許可,僅關聯一個 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 : 資源所有者(或客戶端)的主體名稱 (principal name)。 |
4 | authorizationGrantType : 使用的 AuthorizationGrantType 。 |
5 | authorizedScopes : 為客戶端授權的範圍 (scope) 的 Set 。 |
6 | tokens : 特定於已執行授權許可型別的 OAuth2Token 例項(及相關元資料)。 |
7 | attributes : 特定於已執行授權許可型別的附加屬性,例如已認證的 Principal 、OAuth2AuthorizationRequest 等。 |
OAuth2Authorization
及其關聯的 OAuth2Token
例項都有設定的生命週期。新頒發的 OAuth2Token
是活動的,過期或失效(被撤銷)後變為非活動狀態。當所有關聯的 OAuth2Token
例項都非活動時,OAuth2Authorization
(隱式地)變為非活動狀態。每個 OAuth2Token
都儲存在 OAuth2Authorization.Token
中,該物件提供 isExpired()
、isInvalidated()
和 isActive()
等訪問方法。
OAuth2Authorization.Token
還提供 getClaims()
方法,用於返回與 OAuth2Token
關聯的宣告 (claim)(如果有)。
OAuth2AuthorizationService
OAuth2AuthorizationService
是用於儲存新授權和查詢現有授權的核心元件。其他元件在執行特定協議流程時(例如客戶端認證、授權許可處理、令牌內省、令牌撤銷、動態客戶端註冊等)會使用它。
提供了 InMemoryOAuth2AuthorizationService
和 JdbcOAuth2AuthorizationService
兩種 OAuth2AuthorizationService
實現。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
許可流程,其中客戶端請求範圍 (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 : 資源所有者的主體名稱 (principal name)。 |
3 | authorities : 資源所有者授予客戶端的許可權 (authority)。一個許可權可以代表一個範圍 (scope)、一個宣告 (claim)、一個許可 (permission)、一個角色 (role) 等。 |
OAuth2AuthorizationConsentService
OAuth2AuthorizationConsentService
是用於儲存新授權同意和查詢現有授權同意的核心元件。它主要由實現 OAuth2 授權請求流程的元件使用,例如 authorization_code
許可。
提供了 InMemoryOAuth2AuthorizationConsentService
和 JdbcOAuth2AuthorizationConsentService
兩種 OAuth2AuthorizationConsentService
實現。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
提供以下訪問器 (accessor)
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() : 為客戶端授權的範圍 (scope)。 |
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
的值為
-
code
時,生成OAuth2AuthorizationCode
。 -
access_token
時,生成OAuth2AccessToken
。 -
refresh_token
時,生成OAuth2RefreshToken
。 -
id_token
時,生成OidcIdToken
。
此外,生成的 OAuth2AccessToken
的格式會根據為 RegisteredClient
配置的 TokenSettings.getAccessTokenFormat()
而變化。如果格式是 OAuth2TokenFormat.SELF_CONTAINED
(預設),則生成一個 Jwt
。如果格式是 OAuth2TokenFormat.REFERENCE
,則生成一個“不透明”令牌 (opaque token)。
最後,如果生成的 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
(實現 OAuth2TokenContext
)的 OAuth2TokenCustomizer<OAuth2TokenClaimsContext>
提供了自定義“不透明”OAuth2AccessToken
宣告 (claim) 的能力。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
(實現 OAuth2TokenContext
)的 OAuth2TokenCustomizer<JwtEncodingContext>
提供了自定義 Jwt
的頭部 (header) 和宣告 (claim) 的能力。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 UserInfo 響應。 |
SessionRegistry
如果啟用了 OpenID Connect 1.0,則使用 SessionRegistry
例項來跟蹤已認證的會話。與 OAuth2 授權端點關聯的 SessionAuthenticationStrategy
的預設實現使用 SessionRegistry
來註冊新的已認證會話。
如果沒有註冊 SessionRegistry 的 @Bean ,將使用預設實現 SessionRegistryImpl 。 |
如果註冊了 SessionRegistry 的 @Bean 並且它是 SessionRegistryImpl 的例項,則也應該註冊一個 HttpSessionEventPublisher 的 @Bean ,因為它負責將會話生命週期事件(例如 SessionDestroyedEvent )通知給 SessionRegistryImpl ,從而提供移除 SessionInformation 例項的能力。 |
當終端使用者請求登出時,OpenID Connect 1.0 登出端點使用 SessionRegistry
查詢與已認證終端使用者關聯的 SessionInformation
來執行登出操作。
如果正在使用 Spring Security 的併發會話控制功能,建議註冊一個 SessionRegistry
的 @Bean
,以確保它在 Spring Security 的併發會話控制和 Spring Authorization Server 的登出功能之間共享。
以下示例展示瞭如何註冊一個 SessionRegistry
的 @Bean
和一個 HttpSessionEventPublisher
的 @Bean
(SessionRegistryImpl
所需)
@Bean
public SessionRegistry sessionRegistry() {
return new SessionRegistryImpl();
}
@Bean
public HttpSessionEventPublisher httpSessionEventPublisher() {
return new HttpSessionEventPublisher();
}