核心模型 / 组件

RegisteredClient(已注册客户端)

RegisteredClient是在授权服务器注册的客户端的表示。客户端必须在授权服务器注册后才能启动授权授予流程,例如authorization_codeclient_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的NoOpPasswordEncoderPasswordEncoder 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: 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_basicclient_secret_postprivate_key_jwtclient_secret_jwtnone(公共客户端)
8 authorizationGrantTypes:客户端可以使用的授权授予类型。支持的值包括authorization_codeclient_credentialsrefresh_tokenurn:ietf:params:oauth:grant-type:device_code
9 redirectUris:客户端可以在基于重定向的流程中使用的注册的重定向URI。例如,authorization_code授予。
10 postLogoutRedirectUris:客户端可以用于注销的后重定向URI。
11 scopes:客户端被允许请求的范围。
12 clientSettings:客户端的自定义设置,例如需要PKCE、需要授权同意等。
13 tokenSettings:发放给客户端的OAuth2令牌的自定义设置,例如访问/刷新令牌的生存时间、重用刷新令牌等。

RegisteredClientRepository

RegisteredClientRepository是一个中心组件,用于注册新客户端和查询现有客户端。在遵循特定协议流程时,其他组件会使用它,例如客户端认证、授权授予处理、令牌检查、动态客户端注册等。

RegisteredClientRepository的提供的实现有InMemoryRegisteredClientRepositoryJdbcRegisteredClientRepositoryInMemoryRegisteredClientRepository实现将RegisteredClient实例存储在内存中,建议在开发和测试期间使用。 JdbcRegisteredClientRepository是一个使用JdbcOperations持久化RegisteredClient实例的JDBC实现。

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 =
		new OAuth2AuthorizationServerConfigurer();
	http.apply(authorizationServerConfigurer);

	authorizationServerConfigurer
		.registeredClientRepository(registeredClientRepository);

	...

	return http.build();
}
OAuth2AuthorizationServerConfigurer在同时应用多个配置选项时非常有用。

OAuth2Authorization

OAuth2Authorization是OAuth2授权的表示,它保存与授权相关的状态,该授权由资源所有者或在client_credentials授权授予类型的情况下由客户端本身授予给客户端

Spring Security的OAuth2客户端支持中对应的授权模型是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:资源所有者(或客户端)的主体名称。
4 authorizationGrantType:使用的AuthorizationGrantType
5 authorizedScopes:为客户端授权的作用域的Set
6 tokens:与执行的授权授予类型相关的OAuth2Token实例(及相关元数据)。
7 attributes:与执行的授权授予类型相关的其他属性 - 例如,经过身份验证的PrincipalOAuth2AuthorizationRequest等。

OAuth2Authorization及其关联的OAuth2Token实例具有一定的生命周期。新发行的OAuth2Token在到期或失效(被撤销)时变为无效。当所有关联的OAuth2Token实例都无效时,OAuth2Authorization(隐式地)变为无效。每个OAuth2Token都保存在一个OAuth2Authorization.Token中,提供了isExpired()isInvalidated()isActive()的访问器。

OAuth2Authorization.Token还提供了getClaims(),返回与OAuth2Token相关联的声明(如果有)。

OAuth2AuthorizationService

OAuth2AuthorizationService是一个中心组件,用于存储新的授权并查询现有的授权。在遵循特定协议流程时,其他组件会使用它 - 例如,客户端认证、授权授予处理、令牌内省、令牌撤销、动态客户端注册等。

OAuth2AuthorizationService的提供的实现有InMemoryOAuth2AuthorizationServiceJdbcOAuth2AuthorizationServiceInMemoryOAuth2AuthorizationService实现将OAuth2Authorization实例存储在内存中,建议在开发和测试期间使用。 JdbcOAuth2AuthorizationService是一个使用JdbcOperations持久化OAuth2Authorization实例的JDBC实现。

OAuth2AuthorizationService是一个可选组件,默认为InMemoryOAuth2AuthorizationService

以下示例显示如何注册一个OAuth2AuthorizationService @Bean

@Bean
public OAuth2AuthorizationService authorizationService() {
	return new InMemoryOAuth2AuthorizationService();
}

或者,您可以通过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_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是一个使用JdbcOperations持久化OAuth2AuthorizationConsent实例的JDBC实现。

OAuth2AuthorizationConsentService是一个可选组件,默认为InMemoryOAuth2AuthorizationConsentService

以下示例显示如何注册一个OAuth2AuthorizationConsentService @Bean

@Bean
public OAuth2AuthorizationConsentService authorizationConsentService() {
	return new InMemoryOAuth2AuthorizationConsentService();
}

或者,您可以通过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。支持的值包括codeaccess_tokenrefresh_tokenid_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,则生成一个“opaque”令牌。

最后,如果生成的OAuth2Token具有一组声明并实现了ClaimAccessor,则可以从OAuth2Authorization.Token.getClaims()访问这些声明。

OAuth2TokenGenerator主要由实现授权授予处理的组件使用,例如authorization_codeclient_credentialsrefresh_token

提供的实现包括OAuth2AccessTokenGeneratorOAuth2RefreshTokenGeneratorJwtGeneratorOAuth2AccessTokenGenerator生成一个“opaque”(OAuth2TokenFormat.REFERENCE)访问令牌,而JwtGenerator生成一个JwtOAuth2TokenFormat.SELF_CONTAINED)。

OAuth2TokenGenerator是一个可选组件,默认为由OAuth2AccessTokenGeneratorOAuth2RefreshTokenGenerator组成的DelegatingOAuth2TokenGenerator
如果注册了JwtEncoder @BeanJWKSource<SecurityContext> @Bean,则JwtGenerator还会被包含在DelegatingOAuth2TokenGenerator中。

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);
}

或者,您可以通过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之前自定义OAuth2Token的属性。

声明为OAuth2TokenCustomizer<OAuth2TokenClaimsContext>,泛型类型为OAuth2TokenClaimsContextimplements OAuth2TokenContext),提供了自定义"opaque" 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();
		// 自定义声明

	};
}
如果未提供OAuth2TokenGenerator作为@Bean或未通过OAuth2AuthorizationServerConfigurer进行配置,则将自动配置OAuth2TokenCustomizer<OAuth2TokenClaimsContext> @Bean,并配置为OAuth2AccessTokenGenerator

声明为OAuth2TokenCustomizer<JwtEncodingContext>,泛型类型为JwtEncodingContextimplements OAuth2TokenContext),提供了自定义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)) {
			// 为access_token自定义头部/声明

		} else if (context.getTokenType().getValue().equals(OidcParameterNames.ID_TOKEN)) {
			// 为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授权服务器的注销功能之间共享。

以下示例展示了如何注册SessionRegistry @BeanHttpSessionEventPublisher @BeanSessionRegistryImpl所需):

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

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