核心模型 / 组件
已注册客户端
RegisteredClient 是在授权服务器上注册的客户端的表示形式。
客户端必须先在授权服务器上完成注册,然后才能发起授权许可流程,例如 authorization_code 或 client_credentials。
在客户端注册期间,客户端会被分配一个唯一的客户端标识符、(可选的)客户端密钥(取决于客户端类型),以及与其唯一客户端标识符相关联的元数据。 客户端的元数据范围广泛,既包括面向用户的显示字符串(例如客户端名称),也包括特定于协议流程的项目(例如有效的重定向 URI 列表)。
| Spring Security 的 OAuth2 客户端支持中对应的客户端注册模型是 ClientRegistration。 |
客户端的主要目的是请求访问受保护的资源。 客户端首先通过向授权服务器进行身份验证并出示授权许可(authorization grant)来请求访问Tokens。 授权服务器对客户端和授权许可进行身份验证,如果两者均有效,则颁发一个访问Tokens。 客户端现在可以通过出示该访问Tokens,向资源服务器请求受保护的资源。
以下示例展示了如何配置一个允许执行授权码许可(authorization_code grant)流程以请求访问Tokens的https://datatracker.ietf.org/doc/html/rfc6749#section-4.1:
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 的 NoOpPasswordEncoder 所对应的 ../../../features/authentication/password-storage.html#authentication-password-storage-dpe 标识符。 |
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_basic、client_secret_post、private_key_jwt、client_secret_jwt 和 none (公共客户端)。 |
| 8 | authorizationGrantTypes:客户端可以使用的授权许可类型。支持的值包括 authorization_code、client_credentials、refresh_token、urn:ietf:params:oauth:grant-type:device_code 和 urn:ietf:params:oauth:grant-type:token-exchange。 |
| 9 | redirectUris:客户端在基于重定向的流程(例如 https://datatracker.ietf.org/doc/html/rfc6749#section-3.1.2 授权)中可使用的已注册重定向 URI(s)。 |
| 10 | postLogoutRedirectUris:客户端在注销时可使用的注销后重定向 URI(一个或多个)。 |
| 11 | scopes:客户端被允许请求的范围(scope)。 |
| 12 | clientSettings:客户端的自定义设置——例如,要求PKCE、要求授权同意等。 |
| 13 | tokenSettings:为客户端颁发的 OAuth2 Tokens设置的自定义配置——例如,访问Tokens/刷新Tokens的有效期、是否重用刷新Tokens等。 |
注册客户端仓库
RegisteredClientRepository 是一个核心组件,用于注册新客户端以及查询现有客户端。
在遵循特定协议流程(例如客户端身份验证、授权许可处理、Tokens内省、动态客户端注册等)时,其他组件会使用它。
所提供的 RegisteredClientRepository 实现包括 InMemoryRegisteredClientRepository 和 JdbcRegisteredClientRepository。
InMemoryRegisteredClientRepository 实现在内存中存储 RegisteredClient 实例,仅建议在开发和测试期间使用。
JdbcRegisteredClientRepository 是一个基于 JDBC 的实现,它使用 RegisteredClient 持久化 JdbcOperations 实例。
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 在同时应用多个配置选项时非常有用。 |
OAuth2授权
OAuth2Authorization 是 OAuth2 授权的一种表示形式,它保存了与授予客户端的授权相关的状态信息。该授权由资源所有者授予,或者在 client_credentials 授权许可类型的情况下由客户端自身授予。
| Spring Security 的 OAuth2 客户端支持中对应的授权模型是 OAuth2AuthorizedClient。 |
在成功完成授权授予流程后,将创建一个 OAuth2Authorization,并将其与一个 OAuth2AccessToken、一个(可选的)OAuth2RefreshToken 以及特定于所执行的授权授予类型的其他状态进行关联。
与OAuth2Authorization关联的OAuth2Token实例数量会根据授权授予类型的不同而有所变化。
对于 OAuth2 授权码许可(authorization_code grant),会关联一个 OAuth2AuthorizationCode、一个 OAuth2AccessToken 以及一个(可选的)OAuth2RefreshToken。
对于 OpenID Connect 1.0 授权码模式,会关联一个 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:客户端被授权的范围(scope)的Set集合。 |
| 6 | tokens:与所执行的授权许可类型相关的 OAuth2Token 实例(及其关联的元数据)。 |
| 7 | attributes:特定于所执行的授权许可类型(authorization grant type)的附加属性——例如,已认证的Principal、OAuth2AuthorizationRequest以及其他属性。 |
OAuth2Authorization 及其关联的 OAuth2Token 实例具有固定的生命周期。
新签发的 OAuth2Token 处于活动状态,当它过期或被撤销(失效)时变为非活动状态。
当所有关联的 OAuth2Token 实例均非活动时,OAuth2Authorization 即处于(隐式)非活动状态。
每个 OAuth2Token 都存储在 OAuth2Authorization.Token 中,该存储提供对 isExpired()、isInvalidated() 和 isActive() 的访问器。
OAuth2Authorization.Token 还提供了 getClaims() 方法,该方法返回与 OAuth2Token 关联的声明(如果有的话)。
OAuth2 授权服务
OAuth2AuthorizationService 是存储新授权并查询现有授权的核心组件。
在遵循特定协议流程时,其他组件会使用它——例如客户端身份验证、授权许可处理、Tokens内省、Tokens撤销、动态客户端注册等。
所提供的 OAuth2AuthorizationService 实现包括 InMemoryOAuth2AuthorizationService 和 JdbcOAuth2AuthorizationService。
InMemoryOAuth2AuthorizationService 实现在内存中存储 OAuth2Authorization 实例,仅建议在开发和测试期间使用。
JdbcOAuth2AuthorizationService 是一个基于 JDBC 的实现,它使用 OAuth2Authorization 持久化 JdbcOperations 实例。
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 在同时应用多个配置选项时非常有用。 |
OAuth2授权同意
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)
...
}
| 1 | registeredClientId:唯一标识 RegisteredClient 的 ID。 |
| 2 | principalName:资源所有者的主体名称。 |
| 3 | authorities:资源所有者授予客户端的权限。权限可以表示一个作用域(scope)、声明(claim)、许可(permission)、角色(role)等。 |
OAuth2 授权同意服务
OAuth2AuthorizationConsentService 是用于存储新的授权同意信息以及查询现有授权同意信息的核心组件。
它主要被实现 OAuth2 授权请求流程的组件所使用——例如 authorization_code 授权类型。
提供的 OAuth2AuthorizationConsentService 实现包括 InMemoryOAuth2AuthorizationConsentService 和 JdbcOAuth2AuthorizationConsentService。
InMemoryOAuth2AuthorizationConsentService 实现将 OAuth2AuthorizationConsent 实例存储在内存中,仅建议用于开发和测试环境。
JdbcOAuth2AuthorizationConsentService 是一个基于 JDBC 的实现,它使用 OAuth2AuthorizationConsent 来持久化 JdbcOperations 实例。
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)
...
}
| 1 | getRegisteredClient():与授权许可关联的RegisteredClient。 |
| 2 | getPrincipal():资源所有者(或客户端)的 Authentication 实例。 |
| 3 | getAuthorizationServerContext(): 表示持有授权服务器运行时环境信息的 AuthorizationServerContext 对象。 |
| 4 | getAuthorization():与授权许可关联的OAuth2Authorization。 |
| 5 | getAuthorizedScopes():客户端被授权的范围(scope(s))。 |
| 6 | getTokenType():要生成的 OAuth2TokenType。支持的值包括 code、access_token、refresh_token 和 id_token。 |
| 7 | getAuthorizationGrantType():与授权许可关联的 AuthorizationGrantType。 |
| 8 | getAuthorizationGrant():用于处理授权许可的 Authentication 所使用的 AuthenticationProvider 实例。 |
OAuth2Token生成器
OAuth2TokenGenerator 负责根据所提供的 OAuth2TokenContext 中包含的信息生成一个 #oauth2AuthorizationServer-oauth2-token-context。
生成的 OAuth2Token 主要取决于 OAuth2TokenType 中指定的 OAuth2TokenContext 类型。
例如,当 value 的 OAuth2TokenType 为:
-
code,然后生成OAuth2AuthorizationCode。 -
access_token,然后生成OAuth2AccessToken。 -
refresh_token,然后生成OAuth2RefreshToken。 -
id_token,然后生成OidcIdToken。
此外,所生成的 OAuth2AccessToken 的格式会根据为 RegisteredClient 配置的 #oauth2AuthorizationServer-registered-client 而有所不同。
如果格式为 OAuth2TokenFormat.SELF_CONTAINED(默认值),则会生成一个 Jwt。
如果格式为 OAuth2TokenFormat.REFERENCE,则会生成一个“不透明”Tokens。
最后,如果生成的 OAuth2Token 包含一组声明(claims)并实现了 ClaimAccessor 接口,则可以通过 OAuth2Authorization.Token.getClaims() 访问这些声明。
OAuth2TokenGenerator 主要由实现授权许可处理的组件使用,例如 authorization_code、client_credentials 和 refresh_token。
所提供的实现包括 OAuth2AccessTokenGenerator、OAuth2RefreshTokenGenerator 和 JwtGenerator。
其中,OAuth2AccessTokenGenerator 生成一个“不透明”(OAuth2TokenFormat.REFERENCE)的访问Tokens,而 JwtGenerator 则生成一个 Jwt(OAuth2TokenFormat.SELF_CONTAINED)。
OAuth2TokenGenerator 是一个可选组件,默认使用由 DelegatingOAuth2TokenGenerator 和 OAuth2AccessTokenGenerator 组成的 OAuth2RefreshTokenGenerator。 |
如果注册了 JwtEncoder @Bean 或 JWKSource<SecurityContext> @Bean,则会在 JwtGenerator 中额外组合一个 DelegatingOAuth2TokenGenerator。 |
OAuth2TokenGenerator 提供了极大的灵活性,因为它可以支持任何自定义的 access_token 和 refresh_token Tokens格式。
以下示例展示了如何注册一个 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 之前对其属性进行自定义。
声明一个泛型类型为 OAuth2TokenCustomizer<OAuth2TokenClaimsContext>(OAuth2TokenClaimsContext)的 implements OAuth2TokenContext,可用于自定义“不透明”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。 |
声明一个泛型类型为 OAuth2TokenCustomizer<JwtEncodingContext>(JwtEncodingContext)的 implements OAuth2TokenContext,可以自定义 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。 |
会话注册表
如果启用了 OpenID Connect 1.0,则会使用一个 SessionRegistry 实例来跟踪已认证的会话。
SessionRegistry 会被与OAuth2 授权端点关联的 protocol-endpoints.html#oauth2AuthorizationServer-oauth2-authorization-endpoint 默认实现用于注册新的已认证会话。
如果未注册 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 授权服务器的注销功能之间共享。
以下示例展示了如何注册一个 SessionRegistry @Bean 和 HttpSessionEventPublisher @Bean(SessionRegistryImpl 所需):
@Bean
public SessionRegistry sessionRegistry() {
return new SessionRegistryImpl();
}
@Bean
public HttpSessionEventPublisher httpSessionEventPublisher() {
return new HttpSessionEventPublisher();
}