核心模型/组件

注册客户端

一个 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: https://127.0.0.1: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_codeurn: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 的提供实现是 InMemoryRegisteredClientRepositoryJdbcRegisteredClientRepositoryInMemoryRegisteredClientRepository 实现将 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 =
		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:授权给客户端的范围(集)。
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 是一个 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 =
		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 是一个 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 =
		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,则会生成“不透明”令牌。

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

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

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

OAuth2TokenGenerator 是一个 **可选** 组件,默认情况下是一个由 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);
}

或者,您可以通过 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 配置在一起。

使用 JwtEncodingContextimplements 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 Authorization Server 的注销功能之间共享。

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

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

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