Core Model / Components
RegisteredClient
RegisteredClient
代表一个客户端,它与授权服务器 registered。在客户端可以发起授权授予流程(比如 authorization_code
或 client_credentials
)之前,它必须已在授权服务器上注册。
在客户端注册过程中,客户端将分配一个唯一的 client identifier,(可选的)客户端密码(取决于 client type)及与它的唯一客户端标识符相关联的元数据。该客户端的元数据可以从面向用户的显示字符串(比如客户端名称)到协议流程的特定项目(比如有效重定向 URI 的列表)。
Spring Security 的 OAuth2 客户端支持中对应的客户端注册模式为 ClientRegistration。 |
客户端的主要目的是请求对受保护资源的访问。客户端首先通过使用授权服务器进行身份验证,并出示授权授予来请求访问令牌。授权服务器对客户端和授权授予进行身份验证;如果它们有效,则会颁发访问令牌。现在,客户端可以通过出示访问令牌向资源服务器请求受保护资源。
以下示例展示了如何配置一个 RegisteredClient
,它被允许执行 authorization_code grant 流程以请求访问令牌:
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 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
“已注册客户端”与其独特的客户端标识符关联有元数据(属性),定义如下:
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 : The client identifier. |
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 (public clients)。 |
8 | authorizationGrantTypes :客户端能使用的 authorization grant type(s)。支持的值为 authorization_code , client_credentials , refresh_token , 以及 urn:ietf:params:oauth:grant-type:device_code 。 |
9 | redirectUris :已注册的 redirect URI(s),客户端在基于重定向的流程中可以使用,例如 authorization_code 授权。 |
10 | postLogoutRedirectUris :客户端可能用于注销的后注销重定向 URI。 |
11 | scopes :客户端允许请求的范围。 |
12 | clientSettings :客户端的自定义设置,例如,require PKCE,require authorization consent 以及其他设置。 |
13 | tokenSettings :颁发给客户端的 OAuth2 令牌的自定义设置——例如,访问/刷新令牌生存期,重新使用刷新令牌,等等。 |
RegisteredClientRepository
“已注册客户端存储库”是注册新客户端和查询现有客户端的中心组件。在关注特定的协议流(如客户端认证、授权授权处理、令牌自省、动态客户端注册等)时,其他组件会使用它。
提供的“已注册客户端存储库”实现有“内存中已注册客户端存储库”和“JDBC 已注册客户端存储库”。“内存中已注册客户端存储库”实现将“已注册客户端”实例存储在内存中,*仅*建议在开发和测试期间使用。“JDBC 已注册客户端存储库”是使用“JDBC 操作”来持久存储“已注册客户端”实例的 JDBC 实现。
|
下面的示例展示了如何注册一个“已注册客户端存储库”@Bean
:
@Bean
public RegisteredClientRepository registeredClientRepository() {
List<RegisteredClient> registrations = ...
return new InMemoryRegisteredClientRepository(registrations);
}
或者,你可以通过 xref:configuration-model.adoc#customizing-the-configuration[OAuth2AuthorizationServerConfigurer
配置 RegisteredClientRepository
:
@Bean
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
OAuth2AuthorizationServerConfigurer authorizationServerConfigurer =
new OAuth2AuthorizationServerConfigurer();
http.apply(authorizationServerConfigurer);
authorizationServerConfigurer
.registeredClientRepository(registeredClientRepository);
...
return http.build();
}
当同时应用多个配置选项时, |
OAuth2Authorization
OAuth2Authorization
是一个 OAuth2 授权的表示形式,其中包含与授权授予给 client 时所相关的状态,授权授予是资源所有者或在 client_credentials
授权授予类型的情况下本身授予的。
Spring Security 的 OAuth2 客户端支持中对应的授权模型为 OAuth2AuthorizedClient。 |
在成功完成授权授予流程后,将创建一个 OAuth2Authorization
,并关联一个 OAuth2AccessToken
,一个(可选的)https://docs.spring.io/spring-security/site/docs/current/api/org/springframework/security/oauth2/core/OAuth2RefreshToken.html[OAuth2RefreshToken
],以及执行授权授予类型的附加特定状态。
与 OAuth2Authorization
关联的 OAuth2Token
实例因授权授予类型而异。
对于 OAuth2 authorization_code grant,一个 OAuth2AuthorizationCode
,一个 OAuth2AccessToken`和一个(可选的)`OAuth2RefreshToken
是相关的。
对于 OpenID Connect 1.0 authorization_code grant,一个 OAuth2AuthorizationCode
,一个 OidcIdToken
,一个 OAuth2AccessToken
,和一个(可选的)OAuth2RefreshToken
是相关的。
对于 OAuth2 client_credentials grant,仅一个 OAuth2AccessToken
是相关的。
“OAuth2 授权”及其属性定义如下:
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 : The AuthorizationGrantType used. |
5 | authorizedScopes : 客户授权范围的`Set`。 |
6 | tokens : 特定于已执行授权授予类型的`OAuth2Token`实例(和关联元数据)。 |
7 | attributes : 特定于已执行授权授予类型的其他属性——例如,已验证的`Principal`、`OAuth2AuthorizationRequest`和其他内容。 |
“OAuth2 授权”及其关联的 OAuth2Token
实例具有一个设置的生存期。新颁发的 OAuth2Token
是处于活动状态的,当它过期或无效(被吊销)时,会变成非活动状态。“OAuth2 授权”在所有关联的 OAuth2Token
实例均为非活动状态时(隐式地)为非活动状态。每个 OAuth2Token
都保存在一个 OAuth2Authorization.Token
中,其中提供对 isExpired()
、isInvalidated()
和 isActive()
的访问器。
OAuth2Authorization.Token
还提供 getClaims()
,用于返回与 OAuth2Token
关联的声明(如果存在)。
OAuth2AuthorizationService
“OAuth2 授权服务”是存储新授权和查询现有授权的中心组件。在关注特定的协议流时,其他组件会使用它 – 例如,客户端认证、授权授权处理、令牌自省、令牌吊销、动态客户端注册等。
提供的“OAuth2 授权服务”实现有“内存中 OAuth2 授权服务”和“JDBC OAuth2 授权服务”。“内存中 OAuth2 授权服务”实现将“OAuth2 授权”实例存储在内存中,*仅*建议在开发和测试期间使用。“JDBC OAuth2 授权服务”是使用“JDBC 操作”来持久存储“OAuth2 授权”实例的 JDBC 实现。
|
下面的示例展示了如何注册一个“OAuth2 授权服务”@Bean
:
@Bean
public OAuth2AuthorizationService authorizationService() {
return new InMemoryOAuth2AuthorizationService();
}
或者,你可以通过 xref:configuration-model.adoc#customizing-the-configuration[OAuth2AuthorizationServerConfigurer
配置 OAuth2AuthorizationService
:
@Bean
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
OAuth2AuthorizationServerConfigurer authorizationServerConfigurer =
new OAuth2AuthorizationServerConfigurer();
http.apply(authorizationServerConfigurer);
authorizationServerConfigurer
.authorizationService(authorizationService);
...
return http.build();
}
当同时应用多个配置选项时, |
OAuth2AuthorizationConsent
一个 OAuth2AuthorizationConsent
代表一个 OAuth2 authorization request flow(例如保持 authorities 授予给 client 的资源所有者的 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
@Bean
:
@Bean
public OAuth2AuthorizationConsentService authorizationConsentService() {
return new InMemoryOAuth2AuthorizationConsentService();
}
或者,你可以通过 xref:configuration-model.adoc#customizing-the-configuration[OAuth2AuthorizationServerConfigurer
配置 OAuth2AuthorizationConsentService
:
@Bean
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
OAuth2AuthorizationServerConfigurer authorizationServerConfigurer =
new OAuth2AuthorizationServerConfigurer();
http.apply(authorizationServerConfigurer);
authorizationServerConfigurer
.authorizationConsentService(authorizationConsentService);
...
return http.build();
}
当同时应用多个配置选项时, |
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
提供了很大的灵活性,因为它可以支持 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);
}
或者,你可以通过 xref:configuration-model.adoc#customizing-the-configuration[OAuth2AuthorizationServerConfigurer
配置 OAuth2TokenGenerator
:
@Bean
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
OAuth2AuthorizationServerConfigurer authorizationServerConfigurer =
new OAuth2AuthorizationServerConfigurer();
http.apply(authorizationServerConfigurer);
authorizationServerConfigurer
.tokenGenerator(tokenGenerator);
...
return http.build();
}
当同时应用多个配置选项时, |
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
};
}
如果没有将 |
声明为通用类型 JwtEncodingContext
(实施 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
}
};
}
如果没有将 |
SessionRegistry
如果启用 OpenID Connect 1.0,一个 SessionRegistry
实例将用于跟踪已验证的会话。SessionRegistry
被 OAuth2 Authorization Endpoint 关联的 SessionAuthenticationStrategy
的默认实现用来注册新的已验证的会话。
如果未注册 |
如果一个 SessionRegistry
@Bean
已注册并且是 SessionRegistryImpl
的一个实例,那么一个 HttpSessionEventPublisher
@Bean
SHOULD 也可注册为它负责通知 SessionRegistryImpl
会话生命周期事件,例如 SessionDestroyedEvent
,以提供删除 SessionInformation
实例的能力。
当注销是由终端用户请求时,OpenID Connect 1.0 Logout Endpoint 使用 SessionRegistry
来查找与已验证的终端用户关联的 SessionInformation
以执行注销。
如果正在使用 Spring Security 的 并发会话控制 功能,则它是 *RECOMMENDED*注册 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();
}