OAuth2
Spring Security 提供全面的 OAuth 2.0 支持。本节讨论如何将 OAuth 2.0 集成到基于 servlet 的应用程序中。
概述
Spring Security 的 OAuth 2.0 支持包含两个主要功能集
OAuth2 登录 是一款非常强大的 OAuth2 客户端功能,在参考文档中值得拥有自己的部分。但是,它并不作为独立功能存在,需要 OAuth2 客户端才能正常运行。 |
这些功能集涵盖了 OAuth 2.0 授权框架 中定义的资源服务器和客户端角色,而授权服务器角色则由 Spring Authorization Server 涵盖,这是一个基于 Spring Security 的独立项目。
OAuth2 中的资源服务器和客户端角色通常由一个或多个服务器端应用程序表示。此外,授权服务器角色可以由一个或多个第三方(例如,在组织内集中身份管理和/或身份验证时)-或-由应用程序(例如,使用 Spring Authorization Server)表示。
例如,典型的基于 OAuth2 的微服务架构可能包含一个面向用户的客户端应用程序、几个提供 REST API 的后端资源服务器以及一个用于管理用户和身份验证问题的第三方授权服务器。通常,单个应用程序仅代表这些角色中的一个,需要与提供其他角色的一个或多个第三方集成。
Spring Security 处理这些场景以及更多场景。以下部分涵盖了 Spring Security 提供的角色,并包含常见场景的示例。
OAuth2 资源服务器
本节包含 OAuth2 资源服务器功能的摘要以及示例。有关完整的参考文档,请参阅 OAuth 2.0 资源服务器。 |
要开始使用,请将 spring-security-oauth2-resource-server
依赖项添加到您的项目中。使用 Spring Boot 时,请添加以下启动器
-
Gradle
-
Maven
implementation 'org.springframework.boot:spring-boot-starter-oauth2-resource-server'
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>
有关不使用 Spring Boot 时的其他选项,请参阅 获取 Spring Security。 |
考虑以下 OAuth2 资源服务器的用例
-
我想使用 OAuth2 保护对 API 的访问(授权服务器提供 JWT 或不透明访问令牌)
-
我想使用 JWT 保护对 API 的访问(自定义令牌)
使用 OAuth2 访问令牌保护访问
使用 OAuth2 访问令牌保护对 API 的访问非常普遍。在大多数情况下,Spring Security 只需要最少的配置即可使用 OAuth2 来保护应用程序。
Spring Security 支持两种类型的 Bearer
令牌,每种类型使用不同的组件进行验证。
JWT 支持
以下示例使用 Spring Boot 配置属性配置 JwtDecoder
bean。
spring:
security:
oauth2:
resourceserver:
jwt:
issuer-uri: https://my-auth-server.com
使用 Spring Boot 时,这已经是必需的全部内容。Spring Boot 提供的默认配置等效于以下内容。
-
Java
-
Kotlin
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests((authorize) -> authorize
.anyRequest().authenticated()
)
.oauth2ResourceServer((oauth2) -> oauth2
.jwt(Customizer.withDefaults())
);
return http.build();
}
@Bean
public JwtDecoder jwtDecoder() {
return JwtDecoders.fromIssuerLocation("https://my-auth-server.com");
}
}
import org.springframework.security.config.annotation.web.invoke
@Configuration
@EnableWebSecurity
class SecurityConfig {
@Bean
fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
http {
authorizeHttpRequests {
authorize(anyRequest, authenticated)
}
oauth2ResourceServer {
jwt { }
}
}
return http.build()
}
@Bean
fun jwtDecoder(): JwtDecoder {
return JwtDecoders.fromIssuerLocation("https://my-auth-server.com")
}
}
不透明令牌支持
以下示例使用 Spring Boot 配置属性配置 OpaqueTokenIntrospector
bean。
spring:
security:
oauth2:
resourceserver:
opaquetoken:
introspection-uri: https://my-auth-server.com/oauth2/introspect
client-id: my-client-id
client-secret: my-client-secret
使用 Spring Boot 时,这已经是必需的全部内容。Spring Boot 提供的默认配置等效于以下内容。
-
Java
-
Kotlin
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests((authorize) -> authorize
.anyRequest().authenticated()
)
.oauth2ResourceServer((oauth2) -> oauth2
.opaqueToken(Customizer.withDefaults())
);
return http.build();
}
@Bean
public OpaqueTokenIntrospector opaqueTokenIntrospector() {
return new SpringOpaqueTokenIntrospector(
"https://my-auth-server.com/oauth2/introspect", "my-client-id", "my-client-secret");
}
}
import org.springframework.security.config.annotation.web.invoke
@Configuration
@EnableWebSecurity
class SecurityConfig {
@Bean
fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
http {
authorizeHttpRequests {
authorize(anyRequest, authenticated)
}
oauth2ResourceServer {
opaqueToken { }
}
}
return http.build()
}
@Bean
fun opaqueTokenIntrospector(): OpaqueTokenIntrospector {
return SpringOpaqueTokenIntrospector(
"https://my-auth-server.com/oauth2/introspect", "my-client-id", "my-client-secret"
)
}
}
使用自定义 JWT 保护访问
使用 JWT 保护对 API 的访问是一个相当常见的目标,尤其是在前端开发为单页应用程序时。Spring Security 中的 OAuth2 资源服务器支持可用于任何类型的 Bearer
令牌,包括自定义 JWT。
使用 JWT 保护 API 所需的全部内容是 JwtDecoder
bean,它用于验证签名和解码令牌。Spring Security 将自动使用提供的 bean 在 SecurityFilterChain
中配置保护。
以下示例使用 Spring Boot 配置属性配置 JwtDecoder
bean。
spring:
security:
oauth2:
resourceserver:
jwt:
public-key-location: classpath:my-public-key.pub
您可以将公钥提供为类路径资源(在本示例中称为 |
使用 Spring Boot 时,这已经是必需的全部内容。Spring Boot 提供的默认配置等效于以下内容。
-
Java
-
Kotlin
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests((authorize) -> authorize
.anyRequest().authenticated()
)
.oauth2ResourceServer((oauth2) -> oauth2
.jwt(Customizer.withDefaults())
);
return http.build();
}
@Bean
public JwtDecoder jwtDecoder() {
return NimbusJwtDecoder.withPublicKey(publicKey()).build();
}
private RSAPublicKey publicKey() {
// ...
}
}
import org.springframework.security.config.annotation.web.invoke
@Configuration
@EnableWebSecurity
class SecurityConfig {
@Bean
fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
http {
authorizeHttpRequests {
authorize(anyRequest, authenticated)
}
oauth2ResourceServer {
jwt { }
}
}
return http.build()
}
@Bean
fun jwtDecoder(): JwtDecoder {
return NimbusJwtDecoder.withPublicKey(publicKey()).build()
}
private fun publicKey(): RSAPublicKey {
// ...
}
}
Spring Security 不提供用于铸造令牌的端点。但是,Spring Security 确实提供了 |
OAuth2 客户端
本节包含 OAuth2 客户端功能的摘要以及示例。有关完整的参考文档,请参见 OAuth 2.0 客户端 和 OAuth 2.0 登录。 |
要开始使用,请将 spring-security-oauth2-client
依赖项添加到您的项目中。使用 Spring Boot 时,请添加以下启动器。
-
Gradle
-
Maven
implementation 'org.springframework.boot:spring-boot-starter-oauth2-client'
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>
有关不使用 Spring Boot 时的其他选项,请参阅 获取 Spring Security。 |
考虑以下 OAuth2 客户端的用例。
使用 OAuth2 登录用户
通常需要用户通过 OAuth2 登录。 OpenID Connect 1.0 提供了一个名为 id_token
的特殊令牌,旨在为 OAuth2 客户端提供执行用户身份验证和登录用户的能力。在某些情况下,OAuth2 可以直接用于登录用户(例如,流行的社交登录提供商(如 GitHub 和 Facebook)没有实现 OpenID Connect)。
以下示例配置应用程序充当能够使用 OAuth2 或 OpenID Connect 登录用户的 OAuth2 客户端。
-
Java
-
Kotlin
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
// ...
.oauth2Login(Customizer.withDefaults());
return http.build();
}
}
import org.springframework.security.config.annotation.web.invoke
@Configuration
@EnableWebSecurity
class SecurityConfig {
@Bean
fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
http {
// ...
oauth2Login { }
}
return http.build()
}
}
除了上述配置之外,应用程序还需要至少一个 ClientRegistration
通过使用 ClientRegistrationRepository
bean 进行配置。以下示例使用 Spring Boot 配置属性配置 InMemoryClientRegistrationRepository
bean。
spring:
security:
oauth2:
client:
registration:
my-oidc-client:
provider: my-oidc-provider
client-id: my-client-id
client-secret: my-client-secret
authorization-grant-type: authorization_code
scope: openid,profile
provider:
my-oidc-provider:
issuer-uri: https://my-oidc-provider.com
使用上述配置,应用程序现在支持两个额外的端点。
-
登录端点(例如
/oauth2/authorization/my-oidc-client
)用于启动登录并执行重定向到第三方授权服务器。 -
重定向端点(例如
/login/oauth2/code/my-oidc-client
)由授权服务器用于重定向回客户端应用程序,并将包含一个code
参数,用于通过访问令牌请求获取id_token
和/或access_token
。
上述配置中 |
访问受保护的资源
向受 OAuth2 保护的第三方 API 发出请求是 OAuth2 客户端的核心用例。这是通过授权客户端(由 Spring Security 中的 OAuth2AuthorizedClient
类表示)并通过在出站请求的 Authorization
标头中放置 Bearer
令牌来访问受保护的资源来实现的。
以下示例配置应用程序充当 OAuth2 客户端,能够从第三方 API 请求受保护的资源。
-
Java
-
Kotlin
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
// ...
.oauth2Client(Customizer.withDefaults());
return http.build();
}
}
import org.springframework.security.config.annotation.web.invoke
@Configuration
@EnableWebSecurity
class SecurityConfig {
@Bean
fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
http {
// ...
oauth2Client { }
}
return http.build()
}
}
以上示例没有提供用户登录的方式。您可以使用任何其他登录机制(例如 |
除了上述配置之外,应用程序还需要至少一个 ClientRegistration
通过使用 ClientRegistrationRepository
bean 进行配置。以下示例使用 Spring Boot 配置属性配置 InMemoryClientRegistrationRepository
bean。
spring:
security:
oauth2:
client:
registration:
my-oauth2-client:
provider: my-auth-server
client-id: my-client-id
client-secret: my-client-secret
authorization-grant-type: authorization_code
scope: message.read,message.write
provider:
my-auth-server:
issuer-uri: https://my-auth-server.com
除了配置 Spring Security 以支持 OAuth2 客户端功能外,您还需要决定如何访问受保护的资源并相应地配置您的应用程序。Spring Security 提供了 OAuth2AuthorizedClientManager
的实现,用于获取可用于访问受保护资源的访问令牌。
当不存在时,Spring Security 会为您注册一个默认的 |
使用 OAuth2AuthorizedClientManager
最简单的方法是通过 ExchangeFilterFunction
,它通过 WebClient
拦截请求。要使用 WebClient
,您需要添加 spring-webflux
依赖项以及一个响应式客户端实现。
-
Gradle
-
Maven
implementation 'org.springframework:spring-webflux'
implementation 'io.projectreactor.netty:reactor-netty'
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webflux</artifactId>
</dependency>
<dependency>
<groupId>io.projectreactor.netty</groupId>
<artifactId>reactor-netty</artifactId>
</dependency>
以下示例使用默认的 OAuth2AuthorizedClientManager
来配置一个 WebClient
,该 WebClient
能够通过在每个请求的 Authorization
标头中放置 Bearer
令牌来访问受保护的资源。
ExchangeFilterFunction
配置 WebClient
-
Java
-
Kotlin
@Configuration
public class WebClientConfig {
@Bean
public WebClient webClient(OAuth2AuthorizedClientManager authorizedClientManager) {
ServletOAuth2AuthorizedClientExchangeFilterFunction filter =
new ServletOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager);
return WebClient.builder()
.apply(filter.oauth2Configuration())
.build();
}
}
@Configuration
class WebClientConfig {
@Bean
fun webClient(authorizedClientManager: OAuth2AuthorizedClientManager): WebClient {
val filter = ServletOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager)
return WebClient.builder()
.apply(filter.oauth2Configuration())
.build()
}
}
此配置的 WebClient
可用于以下示例。
WebClient
访问受保护的资源-
Java
-
Kotlin
import static org.springframework.security.oauth2.client.web.reactive.function.client.ServletOAuth2AuthorizedClientExchangeFilterFunction.clientRegistrationId;
@RestController
public class MessagesController {
private final WebClient webClient;
public MessagesController(WebClient webClient) {
this.webClient = webClient;
}
@GetMapping("/messages")
public ResponseEntity<List<Message>> messages() {
return this.webClient.get()
.uri("https://127.0.0.1:8090/messages")
.attributes(clientRegistrationId("my-oauth2-client"))
.retrieve()
.toEntityList(Message.class)
.block();
}
public record Message(String message) {
}
}
import org.springframework.security.oauth2.client.web.reactive.function.client.ServletOAuth2AuthorizedClientExchangeFilterFunction.clientRegistrationId
@RestController
class MessagesController(private val webClient: WebClient) {
@GetMapping("/messages")
fun messages(): ResponseEntity<List<Message>> {
return webClient.get()
.uri("https://127.0.0.1:8090/messages")
.attributes(clientRegistrationId("my-oauth2-client"))
.retrieve()
.toEntityList<Message>()
.block()!!
}
data class Message(val message: String)
}
访问当前用户的受保护资源
当用户通过 OAuth2 或 OpenID Connect 登录时,授权服务器可能会提供一个访问令牌,该令牌可直接用于访问受保护的资源。这很方便,因为它只需要配置一个 ClientRegistration
即可同时用于这两种用例。
本节将使用 OAuth2 登录用户和访问受保护的资源组合成一个配置。还存在其他高级场景,例如为登录配置一个 |
以下示例配置应用程序充当 OAuth2 客户端,能够登录用户并从第三方 API 请求受保护的资源。
-
Java
-
Kotlin
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
// ...
.oauth2Login(Customizer.withDefaults())
.oauth2Client(Customizer.withDefaults());
return http.build();
}
}
import org.springframework.security.config.annotation.web.invoke
@Configuration
@EnableWebSecurity
class SecurityConfig {
@Bean
fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
http {
// ...
oauth2Login { }
oauth2Client { }
}
return http.build()
}
}
除了上述配置之外,应用程序还需要至少一个 ClientRegistration
通过使用 ClientRegistrationRepository
bean 进行配置。以下示例使用 Spring Boot 配置属性配置 InMemoryClientRegistrationRepository
bean。
spring:
security:
oauth2:
client:
registration:
my-combined-client:
provider: my-auth-server
client-id: my-client-id
client-secret: my-client-secret
authorization-grant-type: authorization_code
scope: openid,profile,message.read,message.write
provider:
my-auth-server:
issuer-uri: https://my-auth-server.com
与之前的示例(使用 OAuth2 登录用户,访问受保护的资源)相比,此示例的主要区别在于通过 |
除了配置 Spring Security 以支持 OAuth2 客户端功能外,您还需要决定如何访问受保护的资源并相应地配置您的应用程序。Spring Security 提供了 OAuth2AuthorizedClientManager
的实现,用于获取可用于访问受保护资源的访问令牌。
当不存在时,Spring Security 会为您注册一个默认的 |
使用 OAuth2AuthorizedClientManager
最简单的方法是通过 ExchangeFilterFunction
,它通过 WebClient
拦截请求。要使用 WebClient
,您需要添加 spring-webflux
依赖项以及一个响应式客户端实现。
-
Gradle
-
Maven
implementation 'org.springframework:spring-webflux'
implementation 'io.projectreactor.netty:reactor-netty'
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webflux</artifactId>
</dependency>
<dependency>
<groupId>io.projectreactor.netty</groupId>
<artifactId>reactor-netty</artifactId>
</dependency>
以下示例使用默认的 OAuth2AuthorizedClientManager
来配置一个 WebClient
,该 WebClient
能够通过在每个请求的 Authorization
标头中放置 Bearer
令牌来访问受保护的资源。
ExchangeFilterFunction
配置 WebClient
-
Java
-
Kotlin
@Configuration
public class WebClientConfig {
@Bean
public WebClient webClient(OAuth2AuthorizedClientManager authorizedClientManager) {
ServletOAuth2AuthorizedClientExchangeFilterFunction filter =
new ServletOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager);
return WebClient.builder()
.apply(filter.oauth2Configuration())
.build();
}
}
@Configuration
class WebClientConfig {
@Bean
fun webClient(authorizedClientManager: OAuth2AuthorizedClientManager): WebClient {
val filter = ServletOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager)
return WebClient.builder()
.apply(filter.oauth2Configuration())
.build()
}
}
此配置的 WebClient
可用于以下示例。
WebClient
访问受保护资源(当前用户)-
Java
-
Kotlin
@RestController
public class MessagesController {
private final WebClient webClient;
public MessagesController(WebClient webClient) {
this.webClient = webClient;
}
@GetMapping("/messages")
public ResponseEntity<List<Message>> messages() {
return this.webClient.get()
.uri("https://127.0.0.1:8090/messages")
.retrieve()
.toEntityList(Message.class)
.block();
}
public record Message(String message) {
}
}
@RestController
class MessagesController(private val webClient: WebClient) {
@GetMapping("/messages")
fun messages(): ResponseEntity<List<Message>> {
return webClient.get()
.uri("https://127.0.0.1:8090/messages")
.retrieve()
.toEntityList<Message>()
.block()!!
}
data class Message(val message: String)
}
与 之前的示例 不同,请注意我们不需要告诉 Spring Security 我们想要使用的 |
启用扩展授权类型
一个常见的用例涉及启用和/或配置扩展授权类型。例如,Spring Security 支持 jwt-bearer
和 token-exchange
授权类型,但默认情况下不会启用它们,因为它们不是核心 OAuth 2.0 规范的一部分。
使用 Spring Security 6.2 及更高版本,我们可以简单地发布一个或多个 OAuth2AuthorizedClientProvider
的 bean,它们将被自动拾取。以下示例仅启用 jwt-bearer
授权类型
jwt-bearer
授权类型-
Java
-
Kotlin
@Configuration
public class SecurityConfig {
@Bean
public OAuth2AuthorizedClientProvider jwtBearer() {
return new JwtBearerOAuth2AuthorizedClientProvider();
}
}
@Configuration
class SecurityConfig {
@Bean
fun jwtBearer(): OAuth2AuthorizedClientProvider {
return JwtBearerOAuth2AuthorizedClientProvider()
}
}
当没有提供默认 OAuth2AuthorizedClientManager
时,Spring Security 将自动发布一个默认 OAuth2AuthorizedClientManager
。
任何自定义 |
为了在 Spring Security 6.2 之前实现上述配置,我们必须自己发布此 bean 并确保我们也重新启用了默认授权类型。为了理解幕后配置的内容,以下是配置可能的样子
jwt-bearer
授权类型(6.2 之前)-
Java
-
Kotlin
@Configuration
public class SecurityConfig {
@Bean
public OAuth2AuthorizedClientManager authorizedClientManager(
ClientRegistrationRepository clientRegistrationRepository,
OAuth2AuthorizedClientRepository authorizedClientRepository) {
OAuth2AuthorizedClientProvider authorizedClientProvider =
OAuth2AuthorizedClientProviderBuilder.builder()
.authorizationCode()
.refreshToken()
.clientCredentials()
.password()
.provider(new JwtBearerOAuth2AuthorizedClientProvider())
.build();
DefaultOAuth2AuthorizedClientManager authorizedClientManager =
new DefaultOAuth2AuthorizedClientManager(
clientRegistrationRepository, authorizedClientRepository);
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
return authorizedClientManager;
}
}
@Configuration
class SecurityConfig {
@Bean
fun authorizedClientManager(
clientRegistrationRepository: ClientRegistrationRepository,
authorizedClientRepository: OAuth2AuthorizedClientRepository
): OAuth2AuthorizedClientManager {
val authorizedClientProvider = OAuth2AuthorizedClientProviderBuilder.builder()
.authorizationCode()
.refreshToken()
.clientCredentials()
.password()
.provider(JwtBearerOAuth2AuthorizedClientProvider())
.build()
val authorizedClientManager = DefaultOAuth2AuthorizedClientManager(
clientRegistrationRepository, authorizedClientRepository
)
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider)
return authorizedClientManager
}
}
自定义现有授权类型
通过发布 bean 来 启用扩展授权类型 的能力也提供了自定义现有授权类型的机会,而无需重新定义默认值。例如,如果我们想自定义 client_credentials
授权的 OAuth2AuthorizedClientProvider
的时钟偏差,我们可以简单地发布一个 bean,如下所示
-
Java
-
Kotlin
@Configuration
public class SecurityConfig {
@Bean
public OAuth2AuthorizedClientProvider clientCredentials() {
ClientCredentialsOAuth2AuthorizedClientProvider authorizedClientProvider =
new ClientCredentialsOAuth2AuthorizedClientProvider();
authorizedClientProvider.setClockSkew(Duration.ofMinutes(5));
return authorizedClientProvider;
}
}
@Configuration
class SecurityConfig {
@Bean
fun clientCredentials(): OAuth2AuthorizedClientProvider {
val authorizedClientProvider = ClientCredentialsOAuth2AuthorizedClientProvider()
authorizedClientProvider.setClockSkew(Duration.ofMinutes(5))
return authorizedClientProvider
}
}
自定义令牌请求参数
在获取访问令牌时需要自定义请求参数是相当常见的。例如,假设我们想在令牌请求中添加一个自定义 audience
参数,因为提供者需要此参数才能使用 authorization_code
授权。
使用 Spring Security 6.2 及更高版本,我们可以简单地发布一个类型为 OAuth2AccessTokenResponseClient
的 bean,其泛型类型为 OAuth2AuthorizationCodeGrantRequest
,它将被 Spring Security 用于配置 OAuth2 客户端组件。
以下示例自定义了 authorization_code
授权的令牌请求参数,没有使用 DSL
-
Java
-
Kotlin
@Configuration
public class SecurityConfig {
@Bean
public OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> authorizationCodeAccessTokenResponseClient() {
OAuth2AuthorizationCodeGrantRequestEntityConverter requestEntityConverter =
new OAuth2AuthorizationCodeGrantRequestEntityConverter();
requestEntityConverter.addParametersConverter(parametersConverter());
DefaultAuthorizationCodeTokenResponseClient accessTokenResponseClient =
new DefaultAuthorizationCodeTokenResponseClient();
accessTokenResponseClient.setRequestEntityConverter(requestEntityConverter);
return accessTokenResponseClient;
}
private static Converter<OAuth2AuthorizationCodeGrantRequest, MultiValueMap<String, String>> parametersConverter() {
return (grantRequest) -> {
MultiValueMap<String, String> parameters = new LinkedMultiValueMap<>();
parameters.set("audience", "xyz_value");
return parameters;
};
}
}
@Configuration
class SecurityConfig {
@Bean
fun authorizationCodeAccessTokenResponseClient(): OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> {
val requestEntityConverter = OAuth2AuthorizationCodeGrantRequestEntityConverter()
requestEntityConverter.addParametersConverter(parametersConverter())
val accessTokenResponseClient = DefaultAuthorizationCodeTokenResponseClient()
accessTokenResponseClient.setRequestEntityConverter(requestEntityConverter)
return accessTokenResponseClient
}
private fun parametersConverter(): Converter<OAuth2AuthorizationCodeGrantRequest, MultiValueMap<String, String>> {
return Converter<OAuth2AuthorizationCodeGrantRequest, MultiValueMap<String, String>> { grantRequest ->
LinkedMultiValueMap<String, String>().also { parameters ->
parameters["audience"] = "xyz_value"
}
}
}
}
请注意,在这种情况下,我们不需要自定义 |
在 Spring Security 6.2 之前,我们必须确保此自定义配置适用于使用 Spring Security DSL 的 OAuth2 登录(如果我们使用此功能)和 OAuth2 客户端组件。为了了解幕后配置的内容,以下是配置可能的样子
-
Java
-
Kotlin
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
OAuth2AuthorizationCodeGrantRequestEntityConverter requestEntityConverter =
new OAuth2AuthorizationCodeGrantRequestEntityConverter();
requestEntityConverter.addParametersConverter(parametersConverter());
DefaultAuthorizationCodeTokenResponseClient accessTokenResponseClient =
new DefaultAuthorizationCodeTokenResponseClient();
accessTokenResponseClient.setRequestEntityConverter(requestEntityConverter);
http
.authorizeHttpRequests((authorize) -> authorize
.anyRequest().authenticated()
)
.oauth2Login((oauth2Login) -> oauth2Login
.tokenEndpoint((tokenEndpoint) -> tokenEndpoint
.accessTokenResponseClient(accessTokenResponseClient)
)
)
.oauth2Client((oauth2Client) -> oauth2Client
.authorizationCodeGrant((authorizationCode) -> authorizationCode
.accessTokenResponseClient(accessTokenResponseClient)
)
);
return http.build();
}
private static Converter<OAuth2AuthorizationCodeGrantRequest, MultiValueMap<String, String>> parametersConverter() {
// ...
}
}
import org.springframework.security.config.annotation.web.invoke
@Configuration
@EnableWebSecurity
class SecurityConfig {
@Bean
fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
val requestEntityConverter = OAuth2AuthorizationCodeGrantRequestEntityConverter()
requestEntityConverter.addParametersConverter(parametersConverter())
val tokenResponseClient = DefaultAuthorizationCodeTokenResponseClient()
tokenResponseClient.setRequestEntityConverter(requestEntityConverter)
http {
authorizeHttpRequests {
authorize(anyRequest, authenticated)
}
oauth2Login {
tokenEndpoint {
accessTokenResponseClient = tokenResponseClient
}
}
oauth2Client {
authorizationCodeGrant {
accessTokenResponseClient = tokenResponseClient
}
}
}
return http.build()
}
private fun parametersConverter(): Converter<OAuth2AuthorizationCodeGrantRequest, MultiValueMap<String, String>> {
// ...
}
}
对于其他授权类型,我们可以发布额外的OAuth2AccessTokenResponseClient
bean 来覆盖默认值。例如,要为client_credentials
授权类型自定义令牌请求,我们可以发布以下 bean
-
Java
-
Kotlin
@Configuration
public class SecurityConfig {
@Bean
public OAuth2AccessTokenResponseClient<OAuth2ClientCredentialsGrantRequest> clientCredentialsAccessTokenResponseClient() {
OAuth2ClientCredentialsGrantRequestEntityConverter requestEntityConverter =
new OAuth2ClientCredentialsGrantRequestEntityConverter();
requestEntityConverter.addParametersConverter(parametersConverter());
DefaultClientCredentialsTokenResponseClient accessTokenResponseClient =
new DefaultClientCredentialsTokenResponseClient();
accessTokenResponseClient.setRequestEntityConverter(requestEntityConverter);
return accessTokenResponseClient;
}
private static Converter<OAuth2ClientCredentialsGrantRequest, MultiValueMap<String, String>> parametersConverter() {
// ...
}
}
@Configuration
class SecurityConfig {
@Bean
fun clientCredentialsAccessTokenResponseClient(): OAuth2AccessTokenResponseClient<OAuth2ClientCredentialsGrantRequest> {
val requestEntityConverter = OAuth2ClientCredentialsGrantRequestEntityConverter()
requestEntityConverter.addParametersConverter(parametersConverter())
val accessTokenResponseClient = DefaultClientCredentialsTokenResponseClient()
accessTokenResponseClient.setRequestEntityConverter(requestEntityConverter)
return accessTokenResponseClient
}
private fun parametersConverter(): Converter<OAuth2ClientCredentialsGrantRequest, MultiValueMap<String, String>> {
// ...
}
}
Spring Security 自动解析以下类型的OAuth2AccessTokenResponseClient
bean
-
OAuth2AuthorizationCodeGrantRequest
(参见DefaultAuthorizationCodeTokenResponseClient
) -
OAuth2RefreshTokenGrantRequest
(参见DefaultRefreshTokenTokenResponseClient
) -
OAuth2ClientCredentialsGrantRequest
(参见DefaultClientCredentialsTokenResponseClient
) -
OAuth2PasswordGrantRequest
(参见DefaultPasswordTokenResponseClient
) -
JwtBearerGrantRequest
(参见DefaultJwtBearerTokenResponseClient
) -
TokenExchangeGrantRequest
(参见DefaultTokenExchangeTokenResponseClient
)
发布类型为 |
发布类型为 |
自定义 OAuth2 客户端组件使用的RestOperations
另一个常见用例是需要自定义获取访问令牌时使用的RestOperations
。我们可能需要这样做来自定义响应的处理(通过自定义HttpMessageConverter
)或为公司网络应用代理设置(通过自定义ClientHttpRequestFactory
)。
使用 Spring Security 6.2 及更高版本,我们可以简单地发布类型为OAuth2AccessTokenResponseClient
的 bean,Spring Security 将为我们配置和发布OAuth2AuthorizedClientManager
bean。
以下示例自定义了所有支持的授权类型的RestOperations
RestOperations
-
Java
-
Kotlin
@Configuration
public class SecurityConfig {
@Bean
public OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> authorizationCodeAccessTokenResponseClient() {
DefaultAuthorizationCodeTokenResponseClient accessTokenResponseClient =
new DefaultAuthorizationCodeTokenResponseClient();
accessTokenResponseClient.setRestOperations(restTemplate());
return accessTokenResponseClient;
}
@Bean
public OAuth2AccessTokenResponseClient<OAuth2RefreshTokenGrantRequest> refreshTokenAccessTokenResponseClient() {
DefaultRefreshTokenTokenResponseClient accessTokenResponseClient =
new DefaultRefreshTokenTokenResponseClient();
accessTokenResponseClient.setRestOperations(restTemplate());
return accessTokenResponseClient;
}
@Bean
public OAuth2AccessTokenResponseClient<OAuth2ClientCredentialsGrantRequest> clientCredentialsAccessTokenResponseClient() {
DefaultClientCredentialsTokenResponseClient accessTokenResponseClient =
new DefaultClientCredentialsTokenResponseClient();
accessTokenResponseClient.setRestOperations(restTemplate());
return accessTokenResponseClient;
}
@Bean
public OAuth2AccessTokenResponseClient<OAuth2PasswordGrantRequest> passwordAccessTokenResponseClient() {
DefaultPasswordTokenResponseClient accessTokenResponseClient =
new DefaultPasswordTokenResponseClient();
accessTokenResponseClient.setRestOperations(restTemplate());
return accessTokenResponseClient;
}
@Bean
public OAuth2AccessTokenResponseClient<JwtBearerGrantRequest> jwtBearerAccessTokenResponseClient() {
DefaultJwtBearerTokenResponseClient accessTokenResponseClient =
new DefaultJwtBearerTokenResponseClient();
accessTokenResponseClient.setRestOperations(restTemplate());
return accessTokenResponseClient;
}
@Bean
public OAuth2AccessTokenResponseClient<TokenExchangeGrantRequest> tokenExchangeAccessTokenResponseClient() {
DefaultTokenExchangeTokenResponseClient accessTokenResponseClient =
new DefaultTokenExchangeTokenResponseClient();
accessTokenResponseClient.setRestOperations(restTemplate());
return accessTokenResponseClient;
}
@Bean
public RestTemplate restTemplate() {
// ...
}
}
@Configuration
class SecurityConfig {
@Bean
fun authorizationCodeAccessTokenResponseClient(): OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> {
val accessTokenResponseClient = DefaultAuthorizationCodeTokenResponseClient()
accessTokenResponseClient.setRestOperations(restTemplate())
return accessTokenResponseClient
}
@Bean
fun refreshTokenAccessTokenResponseClient(): OAuth2AccessTokenResponseClient<OAuth2RefreshTokenGrantRequest> {
val accessTokenResponseClient = DefaultRefreshTokenTokenResponseClient()
accessTokenResponseClient.setRestOperations(restTemplate())
return accessTokenResponseClient
}
@Bean
fun clientCredentialsAccessTokenResponseClient(): OAuth2AccessTokenResponseClient<OAuth2ClientCredentialsGrantRequest> {
val accessTokenResponseClient = DefaultClientCredentialsTokenResponseClient()
accessTokenResponseClient.setRestOperations(restTemplate())
return accessTokenResponseClient
}
@Bean
fun passwordAccessTokenResponseClient(): OAuth2AccessTokenResponseClient<OAuth2PasswordGrantRequest> {
val accessTokenResponseClient = DefaultPasswordTokenResponseClient()
accessTokenResponseClient.setRestOperations(restTemplate())
return accessTokenResponseClient
}
@Bean
fun jwtBearerAccessTokenResponseClient(): OAuth2AccessTokenResponseClient<JwtBearerGrantRequest> {
val accessTokenResponseClient = DefaultJwtBearerTokenResponseClient()
accessTokenResponseClient.setRestOperations(restTemplate())
return accessTokenResponseClient
}
@Bean
fun tokenExchangeAccessTokenResponseClient(): OAuth2AccessTokenResponseClient<TokenExchangeGrantRequest> {
val accessTokenResponseClient = DefaultTokenExchangeTokenResponseClient()
accessTokenResponseClient.setRestOperations(restTemplate())
return accessTokenResponseClient
}
@Bean
fun restTemplate(): RestTemplate {
// ...
}
}
当没有提供默认 OAuth2AuthorizedClientManager
时,Spring Security 将自动发布一个默认 OAuth2AuthorizedClientManager
。
请注意,在这种情况下,我们不需要自定义 |
在 Spring Security 6.2 之前,我们必须确保此自定义配置适用于 OAuth2 登录(如果我们使用此功能)和 OAuth2 客户端组件。我们必须同时使用 Spring Security DSL(用于authorization_code
授权类型)并发布类型为OAuth2AuthorizedClientManager
的 bean 用于其他授权类型。为了了解幕后配置的内容,以下是配置可能的样子
RestOperations
(6.2 之前)-
Java
-
Kotlin
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
DefaultAuthorizationCodeTokenResponseClient accessTokenResponseClient =
new DefaultAuthorizationCodeTokenResponseClient();
accessTokenResponseClient.setRestOperations(restTemplate());
http
// ...
.oauth2Login((oauth2Login) -> oauth2Login
.tokenEndpoint((tokenEndpoint) -> tokenEndpoint
.accessTokenResponseClient(accessTokenResponseClient)
)
)
.oauth2Client((oauth2Client) -> oauth2Client
.authorizationCodeGrant((authorizationCode) -> authorizationCode
.accessTokenResponseClient(accessTokenResponseClient)
)
);
return http.build();
}
@Bean
public OAuth2AuthorizedClientManager authorizedClientManager(
ClientRegistrationRepository clientRegistrationRepository,
OAuth2AuthorizedClientRepository authorizedClientRepository) {
DefaultRefreshTokenTokenResponseClient refreshTokenAccessTokenResponseClient =
new DefaultRefreshTokenTokenResponseClient();
refreshTokenAccessTokenResponseClient.setRestOperations(restTemplate());
DefaultClientCredentialsTokenResponseClient clientCredentialsAccessTokenResponseClient =
new DefaultClientCredentialsTokenResponseClient();
clientCredentialsAccessTokenResponseClient.setRestOperations(restTemplate());
DefaultPasswordTokenResponseClient passwordAccessTokenResponseClient =
new DefaultPasswordTokenResponseClient();
passwordAccessTokenResponseClient.setRestOperations(restTemplate());
DefaultJwtBearerTokenResponseClient jwtBearerAccessTokenResponseClient =
new DefaultJwtBearerTokenResponseClient();
jwtBearerAccessTokenResponseClient.setRestOperations(restTemplate());
JwtBearerOAuth2AuthorizedClientProvider jwtBearerAuthorizedClientProvider =
new JwtBearerOAuth2AuthorizedClientProvider();
jwtBearerAuthorizedClientProvider.setAccessTokenResponseClient(jwtBearerAccessTokenResponseClient);
DefaultTokenExchangeTokenResponseClient tokenExchangeAccessTokenResponseClient =
new DefaultTokenExchangeTokenResponseClient();
tokenExchangeAccessTokenResponseClient.setRestOperations(restTemplate());
TokenExchangeOAuth2AuthorizedClientProvider tokenExchangeAuthorizedClientProvider =
new TokenExchangeOAuth2AuthorizedClientProvider();
tokenExchangeAuthorizedClientProvider.setAccessTokenResponseClient(tokenExchangeAccessTokenResponseClient);
OAuth2AuthorizedClientProvider authorizedClientProvider =
OAuth2AuthorizedClientProviderBuilder.builder()
.authorizationCode()
.refreshToken((refreshToken) -> refreshToken
.accessTokenResponseClient(refreshTokenAccessTokenResponseClient)
)
.clientCredentials((clientCredentials) -> clientCredentials
.accessTokenResponseClient(clientCredentialsAccessTokenResponseClient)
)
.password((password) -> password
.accessTokenResponseClient(passwordAccessTokenResponseClient)
)
.provider(jwtBearerAuthorizedClientProvider)
.provider(tokenExchangeAuthorizedClientProvider)
.build();
DefaultOAuth2AuthorizedClientManager authorizedClientManager =
new DefaultOAuth2AuthorizedClientManager(
clientRegistrationRepository, authorizedClientRepository);
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
return authorizedClientManager;
}
@Bean
public RestTemplate restTemplate() {
// ...
}
}
import org.springframework.security.config.annotation.web.invoke
@Configuration
@EnableWebSecurity
class SecurityConfig {
@Bean
fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
val tokenResponseClient = DefaultAuthorizationCodeTokenResponseClient()
tokenResponseClient.setRestOperations(restTemplate())
http {
// ...
oauth2Login {
tokenEndpoint {
accessTokenResponseClient = tokenResponseClient
}
}
oauth2Client {
authorizationCodeGrant {
accessTokenResponseClient = tokenResponseClient
}
}
}
return http.build()
}
@Bean
fun authorizedClientManager(
clientRegistrationRepository: ClientRegistrationRepository?,
authorizedClientRepository: OAuth2AuthorizedClientRepository?
): OAuth2AuthorizedClientManager {
val refreshTokenAccessTokenResponseClient = DefaultRefreshTokenTokenResponseClient()
refreshTokenAccessTokenResponseClient.setRestOperations(restTemplate())
val clientCredentialsAccessTokenResponseClient = DefaultClientCredentialsTokenResponseClient()
clientCredentialsAccessTokenResponseClient.setRestOperations(restTemplate())
val passwordAccessTokenResponseClient = DefaultPasswordTokenResponseClient()
passwordAccessTokenResponseClient.setRestOperations(restTemplate())
val jwtBearerAccessTokenResponseClient = DefaultJwtBearerTokenResponseClient()
jwtBearerAccessTokenResponseClient.setRestOperations(restTemplate())
val jwtBearerAuthorizedClientProvider = JwtBearerOAuth2AuthorizedClientProvider()
jwtBearerAuthorizedClientProvider.setAccessTokenResponseClient(jwtBearerAccessTokenResponseClient)
val tokenExchangeAccessTokenResponseClient = DefaultTokenExchangeTokenResponseClient()
tokenExchangeAccessTokenResponseClient.setRestOperations(restTemplate())
val tokenExchangeAuthorizedClientProvider = TokenExchangeOAuth2AuthorizedClientProvider()
tokenExchangeAuthorizedClientProvider.setAccessTokenResponseClient(tokenExchangeAccessTokenResponseClient)
val authorizedClientProvider = OAuth2AuthorizedClientProviderBuilder.builder()
.authorizationCode()
.refreshToken { refreshToken ->
refreshToken.accessTokenResponseClient(refreshTokenAccessTokenResponseClient)
}
.clientCredentials { clientCredentials ->
clientCredentials.accessTokenResponseClient(clientCredentialsAccessTokenResponseClient)
}
.password { password ->
password.accessTokenResponseClient(passwordAccessTokenResponseClient)
}
.provider(jwtBearerAuthorizedClientProvider)
.provider(tokenExchangeAuthorizedClientProvider)
.build()
val authorizedClientManager = DefaultOAuth2AuthorizedClientManager(
clientRegistrationRepository, authorizedClientRepository
)
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider)
return authorizedClientManager
}
@Bean
fun restTemplate(): RestTemplate {
// ...
}
}