核心模型/组件

已注册客户端

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:客户端可能在基于重定向的流程(例如authorization_code授权)中使用的已注册重定向 URI
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 授权码授权模式,会关联一个OAuth2AuthorizationCode、一个OAuth2AccessToken和一个(可选的)OAuth2RefreshToken

对于 OpenID Connect 1.0 授权码授权模式,会关联一个OAuth2AuthorizationCode、一个OidcIdToken、一个OAuth2AccessToken和一个(可选的)OAuth2RefreshToken

对于 OAuth2 客户端凭证授权模式,只关联一个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:唯一标识已注册客户端的 ID。
3 principalName:资源所有者(或客户端)的主体名称。
4 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是一个 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 授权请求流程的授权“同意”(决定)——例如,授权码授权模式,它保存资源所有者授予客户端的权限。

在授权访问客户端时,资源所有者可能只授予客户端请求权限的一个子集。典型的用例是授权码授权流程,其中客户端请求范围,资源所有者授予(或拒绝)对请求范围的访问。

完成 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:唯一标识已注册客户端的 ID。
2 principalName:资源所有者的主体名称。
3 authorities:资源所有者授予客户端的权限。权限可以表示范围、声明、权限、角色等。

OAuth2AuthorizationConsentService是存储新的授权同意和查询现有授权同意的中心组件。它主要由实现 OAuth2 授权请求流程的组件使用——例如,授权码授权模式。

提供的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():与授权授权关联的已注册客户端
2 getPrincipal():资源所有者(或客户端)的Authentication实例。
3 getAuthorizationServerContext()AuthorizationServerContext对象,它保存授权服务器运行时环境的信息。
4 getAuthorization():与授权授权关联的OAuth2Authorization
5 getAuthorizedScopes():授权给客户端的范围。
6 getTokenType():要生成的OAuth2TokenType。支持的值为codeaccess_tokenrefresh_tokenid_token
7 getAuthorizationGrantType():与授权授权关联的授权授权类型
8 getAuthorizationGrant():由处理授权授权的AuthenticationProvider使用的Authentication实例。

OAuth2TokenGenerator

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

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

例如,当OAuth2TokenTypevalue为:

  • code时,生成OAuth2AuthorizationCode

  • access_token时,生成OAuth2AccessToken

  • refresh_token时,生成OAuth2RefreshToken

  • id_token时,生成OidcIdToken

此外,生成的OAuth2AccessToken的格式会根据为已注册客户端配置的TokenSettings.getAccessTokenFormat()而有所不同。如果格式为OAuth2TokenFormat.SELF_CONTAINED(默认值),则生成Jwt。如果格式为OAuth2TokenFormat.REFERENCE,则生成“不透明”令牌。

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

OAuth2TokenGenerator主要由实现授权授权处理的组件使用——例如,授权码客户端凭证刷新令牌

提供的实现包括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配置,则将自动使用OAuth2AccessTokenGenerator配置OAuth2TokenCustomizer<OAuth2TokenClaimsContext> @Bean

使用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配置,则将自动使用JwtGenerator配置OAuth2TokenCustomizer<JwtEncodingContext> @Bean

SessionRegistry

如果启用了 OpenID Connect 1.0,则使用SessionRegistry实例来跟踪已认证的会话。SessionRegistry由与OAuth 2.0 授权端点关联的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();
}