核心模型 / 组件
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: http://localhost: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();
}