Core Model / Components

RegisteredClient

RegisteredClient 代表一个客户端,它与授权服务器 registered。在客户端可以发起授权授予流程(比如 authorization_codeclient_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 的 NoOpPasswordEncoderPasswordEncoder 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 实现。

RegisteredClientRepositoryREQUIRED 组件。

下面的示例展示了如何注册一个“已注册客户端存储库”@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();
}

当同时应用多个配置选项时,OAuth2AuthorizationServerConfigurer 很管用。

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 实现。

OAuth2AuthorizationServiceOPTIONAL 组件,默认为 InMemoryOAuth2AuthorizationService

下面的示例展示了如何注册一个“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();
}

当同时应用多个配置选项时,OAuth2AuthorizationServerConfigurer 很管用。

一个 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 是存储新授权同意并查询现有授权同意的中心组件。它主要用于实现 OAuth2 授权请求流的组件,例如 authorization_code 授权。

OAuth2AuthorizationConsentService 提供的实现有 InMemoryOAuth2AuthorizationConsentServiceJdbcOAuth2AuthorizationConsentServiceInMemoryOAuth2AuthorizationConsentService 实现将 OAuth2AuthorizationConsent 实例存储在内存中,*仅*建议在开发和测试中使用。JdbcOAuth2AuthorizationConsentService 是一种 JDBC 实现,它使用 JdbcOperations 来持久存储 OAuth2AuthorizationConsent 实例。

OAuth2AuthorizationConsentServiceOPTIONAL 组件,默认为 InMemoryOAuth2AuthorizationConsentService

以下示例展示了如何注册 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();
}

当同时应用多个配置选项时,OAuth2AuthorizationServerConfigurer 很管用。

OAuth2TokenContext

OAuth2TokenContext 是一个持有与 OAuth2Token 相关的信息的环境对象,其被 OAuth2TokenGeneratorOAuth2TokenCustomizer 使用。

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_tokenrefresh_token`和`id_token
7 getAuthorizationGrantType(): 与授权授予关联的`AuthorizationGrantType`。
8 getAuthorizationGrant(): 处理授权授予的`AuthenticationProvider`使用的`Authentication`实例。

OAuth2TokenGenerator

OAuth2TokenGenerator 负责根据所提供的 OAuth2TokenContext 中包含的信息来生成 OAuth2Token

生成的 OAuth2Token 主要取决于 OAuth2TokenContext 中指定的 OAuth2TokenType 类型。

例如,当 OAuth2TokenTypevalue 为:

  • 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_codeclient_credentialsrefresh_token

提供的实现有 OAuth2AccessTokenGeneratorOAuth2RefreshTokenGeneratorJwtGeneratorOAuth2AccessTokenGenerator 生成一个“不透明”(OAuth2TokenFormat.REFERENCE)访问令牌,而 JwtGenerator 生成一个 Jwt (OAuth2TokenFormat.SELF_CONTAINED)。

OAuth2TokenGeneratorOPTIONAL 组件,默认为由 OAuth2AccessTokenGeneratorOAuth2RefreshTokenGenerator 组成的 DelegatingOAuth2TokenGenerator

如果注册了 JwtEncoder @BeanJWKSource<SecurityContext> @Bean,则会另外在 DelegatingOAuth2TokenGenerator 中组成一个 JwtGenerator

OAuth2TokenGenerator 提供了很大的灵活性,因为它可以支持 access_tokenrefresh_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();
}

当同时应用多个配置选项时,OAuth2AuthorizationServerConfigurer 很管用。

OAuth2TokenCustomizer

OAuth2TokenCustomizer 提供自定义 OAuth2Token 属性的能力,该属性可以在所提供的 OAuth2TokenContext 中访问。它被 OAuth2TokenGenerator 用于在生成 OAuth2Token 之前自定义它的属性。

声明为泛型类型为 OAuth2TokenClaimsContextimplements 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(实施 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 实例将用于跟踪已验证的会话。SessionRegistryOAuth2 Authorization Endpoint 关联的 SessionAuthenticationStrategy 的默认实现用来注册新的已验证的会话。

如果未注册 SessionRegistry @Bean,则将使用默认实现 SessionRegistryImpl

如果一个 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 @BeanHttpSessionEventPublisher @BeanSessionRegistryImpl 所需):

@Bean
public SessionRegistry sessionRegistry() {
	return new SessionRegistryImpl();
}

@Bean
public HttpSessionEventPublisher httpSessionEventPublisher() {
	return new HttpSessionEventPublisher();
}