核心模型/元件
RegisteredClient
RegisteredClient 是一個客戶端的表示,該客戶端已在授權伺服器上註冊。客戶端在啟動授權授予流程(例如 authorization_code 或 client_credentials)之前,必須在授權伺服器上註冊。
在客戶端註冊期間,客戶端會被分配一個唯一的客戶端識別符號,(可選地)一個客戶端金鑰(取決於客戶端型別),以及與其唯一客戶端識別符號關聯的元資料。客戶端的元資料可以從面向人類的顯示字串(如客戶端名稱)到特定於協議流的項(如有效重定向URI列表)不等。
| Spring Security OAuth2 客戶端支援中相應的客戶端註冊模型是 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 的 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 {
http
.oauth2AuthorizationServer((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 {
http
.oauth2AuthorizationServer((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 {
http
.oauth2AuthorizationServer((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,則 JwtGenerator 將額外新增到 DelegatingOAuth2TokenGenerator 中。 |
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 {
http
.oauth2AuthorizationServer((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 配置。 |
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 Security 授權伺服器的登出功能之間共享。
以下示例展示瞭如何註冊 SessionRegistry @Bean 和 HttpSessionEventPublisher @Bean(SessionRegistryImpl 所需)
@Bean
public SessionRegistry sessionRegistry() {
return new SessionRegistryImpl();
}
@Bean
public HttpSessionEventPublisher httpSessionEventPublisher() {
return new HttpSessionEventPublisher();
}