核心模型 / 组件
RegisteredClient
RegisteredClient 是一个在授权服务器中注册的客户端的表示。客户端必须在授权服务器注册后,才能发起授权许可流程,例如 authorization_code 或 client_credentials。
Spring Security OAuth2 客户端支持中对应的客户端注册模型是 ClientRegistration。
客户端的主要目的是请求访问受保护资源。客户端首先通过向授权服务器进行身份验证并出示授权许可来请求访问令牌。授权服务器对客户端和授权许可进行身份验证,如果验证有效,则颁发访问令牌。随后,客户端可以通过出示访问令牌向资源服务器请求受保护资源。
以下示例展示了如何配置一个允许执行授权码授权流程以请求访问令牌的 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();
{noop}代表 Spring Security 中 NoOpPasswordEncoder 的PasswordEncoder标识符。
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
...
}
id: 唯一标识RegisteredClient的 ID。clientId: 客户端标识符。clientIdIssuedAt: 客户端标识符的颁发时间。clientSecret: 客户端的密钥。该值应使用 Spring Security 的 PasswordEncoder 进行编码。clientSecretExpiresAt: 客户端密钥的过期时间。clientName: 客户端的描述性名称。该名称可能在某些场景下使用,例如在同意页面显示客户端名称。clientAuthenticationMethods: 客户端可以使用的身份验证方法。支持的值包括client_secret_basic、client_secret_post、private_key_jwt、client_secret_jwt以及none(公共客户端)。authorizationGrantTypes: 客户端可以使用的 授权许可类型。支持的值包括authorization_code、client_credentials、refresh_token、urn:ietf:params:oauth:grant-type:device_code和urn:ietf:params:oauth:grant-type:token-exchange。redirectUris: 客户端在基于重定向的流程(例如authorization_code授权)中可以使用的已注册 重定向 URI。postLogoutRedirectUris: 客户端可用于注销后的重定向 URI。scopes: 允许客户端请求的权限范围。clientSettings: 客户端的自定义设置——例如,要求使用 PKCE、要求授权同意等。tokenSettings: 颁发给客户端的 OAuth2 令牌的自定义设置——例如,访问/刷新令牌的生存时间、重用刷新令牌等。
RegisteredClientRepository
RegisteredClientRepository 是用于注册新客户端和查询现有客户端的核心组件。当遵循特定协议流程时,例如客户端认证、授权许可处理、令牌内省、动态客户端注册等,其他组件会使用该存储库。
提供的 RegisteredClientRepository 实现包括 InMemoryRegisteredClientRepository 和 JdbcRegisteredClientRepository。InMemoryRegisteredClientRepository 实现将 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 {
http
.oauth2AuthorizationServer((authorizationServer) ->
authorizationServer
.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
...
}
id: 唯一标识OAuth2Authorization的 ID。registeredClientId: 唯一标识 RegisteredClient 的 ID。principalName: 资源所有者(或客户端)的主体名称。authorizationGrantType: 所使用的AuthorizationGrantType。authorizedScopes: 为客户端授权的Set范围集合。tokens: 与执行的授权许可类型相关的OAuth2Token实例(及其关联的元数据)。attributes: 与执行的授权许可类型相关的附加属性——例如,已认证的Principal、OAuth2AuthorizationRequest等。
OAuth2Authorization 及其关联的 OAuth2Token 实例具有设定的生命周期。新颁发的 OAuth2Token 处于活动状态,并在其过期或失效(撤销)时变为非活动状态。当所有关联的 OAuth2Token 实例都处于非活动状态时,OAuth2Authorization 便(隐式地)处于非活动状态。每个 OAuth2Token 都保存在一个 OAuth2Authorization.Token 中,该对象提供了 isExpired()、isInvalidated() 和 isActive() 的访问器。
OAuth2Authorization.Token 还提供了 getClaims() 方法,用于返回与 OAuth2Token 关联的声明(如果有的话)。
OAuth2AuthorizationService
OAuth2AuthorizationService 是存储新授权和查询现有授权的核心组件。其他组件在执行特定协议流程时会使用该服务,例如客户端认证、授权许可处理、令牌内省、令牌撤销、动态客户端注册等场景。
提供的 OAuth2AuthorizationService 实现包括 InMemoryOAuth2AuthorizationService 和 JdbcOAuth2AuthorizationService。InMemoryOAuth2AuthorizationService 实现将 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 {
http
.oauth2AuthorizationServer((authorizationServer) ->
authorizationServer
.authorizationService(authorizationService)
)
...
return http.build();
}
当需要同时应用多个配置选项时,OAuth2AuthorizationServerConfigurer 非常有用。
OAuth2AuthorizationConsent
OAuth2AuthorizationConsent 是 OAuth2 授权请求流程 中授权“同意”(决定)的表示形式——例如,在 authorization_code 授权类型中,它保存了资源所有者授予 客户端 的权限。
在授权客户端访问时,资源所有者可能仅授予客户端所请求权限的一个子集。典型用例是 authorization_code 授权流程,其中客户端请求一个或多个 scope,而资源所有者则批准(或拒绝)对所请求 scope 的访问。
在完成 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
...
}
registeredClientId: 唯一标识RegisteredClient的ID。principalName: 资源所有者的主体名称。authorities: 资源所有者授予客户端的权限。一个权限可以代表一个作用域、一个声明、一个许可、一个角色等。
OAuth2AuthorizationConsentService
OAuth2AuthorizationConsentService 是存储新授权同意书和查询现有授权同意书的核心组件。它主要由实现 OAuth2 授权请求流程的组件使用——例如,authorization_code 授权类型。
提供的 OAuth2AuthorizationConsentService 实现包括 InMemoryOAuth2AuthorizationConsentService 和 JdbcOAuth2AuthorizationConsentService。InMemoryOAuth2AuthorizationConsentService 实现将 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 {
http
.oauth2AuthorizationServer((authorizationServer) ->
authorizationServer
.authorizationConsentService(authorizationConsentService)
)
...
return http.build();
}
当需要同时应用多个配置选项时,OAuth2AuthorizationServerConfigurer 非常有用。
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
...
}
getRegisteredClient(): 与授权许可关联的RegisteredClient。getPrincipal(): 资源所有者(或客户端)的Authentication实例。getAuthorizationServerContext(): 包含授权服务器运行时环境信息的AuthorizationServerContext对象。getAuthorization(): 与授权许可关联的OAuth2Authorization。getAuthorizedScopes(): 为客户端授权的 scope(s)。getTokenType(): 要生成的OAuth2TokenType。支持的值为code、access_token、refresh_token和id_token。getAuthorizationGrantType(): 与授权许可关联的AuthorizationGrantType。getAuthorizationGrant(): 处理授权许可的AuthenticationProvider所使用的Authentication实例。
OAuth2TokenGenerator
OAuth2TokenGenerator 负责根据提供的 OAuth2TokenContext 中包含的信息生成 OAuth2Token。
生成的 OAuth2Token 主要取决于 OAuth2TokenContext 中指定的 OAuth2TokenType 类型。
例如,当 OAuth2TokenType 的 value 为:
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 主要被实现授权许可处理的组件所使用——例如 authorization_code、client_credentials 和 refresh_token。
提供的实现包括 OAuth2AccessTokenGenerator、OAuth2RefreshTokenGenerator 和 JwtGenerator。其中,OAuth2AccessTokenGenerator 用于生成“不透明”(OAuth2TokenFormat.REFERENCE)的访问令牌,而 JwtGenerator 则用于生成 Jwt(OAuth2TokenFormat.SELF_CONTAINED)格式的令牌。
OAuth2TokenGenerator 是一个可选组件,默认情况下是一个由 OAuth2AccessTokenGenerator 和 OAuth2RefreshTokenGenerator 组成的 DelegatingOAuth2TokenGenerator。
如果注册了 JwtEncoder @Bean 或 JWKSource<SecurityContext> @Bean,那么 DelegatingOAuth2TokenGenerator 中会额外组合一个 JwtGenerator。
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);
}
或者,你也可以通过 OAuth2AuthorizationServerConfigurer 来配置 OAuth2TokenGenerator:
@Bean
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
http
.oauth2AuthorizationServer((authorizationServer) ->
authorizationServer
.tokenGenerator(tokenGenerator)
)
...
return http.build();
}
当需要同时应用多个配置选项时,OAuth2AuthorizationServerConfigurer 非常有用。
OAuth2TokenCustomizer
OAuth2TokenCustomizer 提供了自定义 OAuth2Token 属性的能力,这些属性可在提供的 OAuth2TokenContext 中访问。它由 OAuth2TokenGenerator 使用,以便在生成 OAuth2Token 之前自定义其属性。
一个声明为泛型类型 OAuth2TokenClaimsContext(实现 OAuth2TokenContext 接口)的 OAuth2TokenCustomizer<OAuth2TokenClaimsContext> 提供了自定义“不透明” OAuth2AccessToken 声明(claims)的能力。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 头部(headers)和声明(claims)的能力。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实例来跟踪已认证的会话。与OAuth2 授权端点关联的SessionAuthenticationStrategy默认实现会使用SessionRegistry来注册新的已认证会话。
如果未注册 SessionRegistry @Bean,将使用默认实现 SessionRegistryImpl。
如果注册了一个 SessionRegistry @Bean 且其为 SessionRegistryImpl 的实例,则应当同时注册一个 HttpSessionEventPublisher @Bean,因为它负责向 SessionRegistryImpl 通知会话生命周期事件(例如 SessionDestroyedEvent),从而提供移除 SessionInformation 实例的能力。
当终端用户请求登出时,OpenID Connect 1.0 登出端点会使用 SessionRegistry 来查找与已认证终端用户关联的 SessionInformation,以执行登出操作。
如果正在使用 Spring Security 的并发会话控制功能,建议注册一个 SessionRegistry @Bean,以确保它在 Spring Security 的并发会话控制与 Spring Security Authorization Server 的注销功能之间共享。
以下示例展示了如何注册 SessionRegistry @Bean 和 HttpSessionEventPublisher @Bean(SessionRegistryImpl 所需):
@Bean
public SessionRegistry sessionRegistry() {
return new SessionRegistryImpl();
}
@Bean
public HttpSessionEventPublisher httpSessionEventPublisher() {
return new HttpSessionEventPublisher();
}