1
0
mirror of synced 2026-05-22 21:33:16 +00:00

Add RestClient interceptor

Closes gh-13588
This commit is contained in:
Steve Riesenberg
2024-04-30 17:48:17 -05:00
parent b294816600
commit e3c19ba86c
4 changed files with 1432 additions and 71 deletions
+266 -71
View File
@@ -398,13 +398,14 @@ See xref:getting-spring-security.adoc[] for additional options when not using Sp
Consider the following use cases for OAuth2 Client:
* <<oauth2-client-log-users-in,I want to log users in using OAuth 2.0 or OpenID Connect 1.0>>
* <<oauth2-client-access-protected-resources,I want to obtain an access token for users in order to access a third-party API>>
* <<oauth2-client-access-protected-resources-current-user,I want to do both>> (log users in _and_ access a third-party API)
* <<oauth2-client-enable-extension-grant-type,I want to enable an extension grant type>>
* <<oauth2-client-customize-existing-grant-type,I want to customize an existing grant type>>
* <<oauth2-client-customize-request-parameters,I want to customize token request parameters>>
* <<oauth2-client-customize-rest-operations,I want to customize the `RestOperations` used by OAuth2 Client components>>
* I want to <<oauth2-client-log-users-in,log users in using OAuth 2.0 or OpenID Connect 1.0>>
* I want to <<oauth2-client-access-protected-resources,use `RestClient` to obtain an access token for users in order to access a third-party API>>
* I want to <<oauth2-client-access-protected-resources-current-user,do both>> (log users in _and_ access a third-party API)
* I want to <<oauth2-client-access-protected-resources-webclient,use `WebClient` to obtain an access token for users in order to access a third-party API>>
* I want to <<oauth2-client-enable-extension-grant-type,enable an extension grant type>>
* I want to <<oauth2-client-customize-existing-grant-type,customize an existing grant type>>
* I want to <<oauth2-client-customize-request-parameters,customize token request parameters>>
* I want to <<oauth2-client-customize-rest-operations,customize the `RestOperations` used by OAuth2 Client components>>
[[oauth2-client-log-users-in]]
=== Log Users In with OAuth2
@@ -584,38 +585,11 @@ Spring Security provides implementations of `OAuth2AuthorizedClientManager` for
Spring Security registers a default `OAuth2AuthorizedClientManager` bean for you when one does not exist.
====
The easiest way to use an `OAuth2AuthorizedClientManager` is via an `ExchangeFilterFunction` that intercepts requests through a `WebClient`.
To use `WebClient`, you will need to add the `spring-webflux` dependency along with a reactive client implementation:
The easiest way to use an `OAuth2AuthorizedClientManager` is via a `ClientHttpRequestInterceptor` that intercepts requests through a `RestClient`, which is already available when `spring-web` is on the classpath.
.Add Spring WebFlux Dependency
[tabs]
======
Gradle::
+
[source,gradle,role="primary"]
----
implementation 'org.springframework:spring-webflux'
implementation 'io.projectreactor.netty:reactor-netty'
----
The following example uses the default `OAuth2AuthorizedClientManager` to configure a `RestClient` capable of accessing protected resources by placing `Bearer` tokens in the `Authorization` header of each request:
Maven::
+
[source,maven,role="secondary"]
----
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webflux</artifactId>
</dependency>
<dependency>
<groupId>io.projectreactor.netty</groupId>
<artifactId>reactor-netty</artifactId>
</dependency>
----
======
The following example uses the default `OAuth2AuthorizedClientManager` to configure a `WebClient` capable of accessing protected resources by placing `Bearer` tokens in the `Authorization` header of each request:
.Configure `WebClient` with `ExchangeFilterFunction`
.Configure `RestClient` with `ClientHttpRequestInterceptor`
[tabs]
=====
Java::
@@ -623,14 +597,14 @@ Java::
[source,java,role="primary"]
----
@Configuration
public class WebClientConfig {
public class RestClientConfig {
@Bean
public WebClient webClient(OAuth2AuthorizedClientManager authorizedClientManager) {
ServletOAuth2AuthorizedClientExchangeFilterFunction filter =
new ServletOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager);
return WebClient.builder()
.apply(filter.oauth2Configuration())
public RestClient restClient(OAuth2AuthorizedClientManager authorizedClientManager) {
OAuth2ClientHttpRequestInterceptor requestInterceptor =
new OAuth2ClientHttpRequestInterceptor(authorizedClientManager);
return RestClient.builder()
.requestInterceptor(requestInterceptor)
.build();
}
@@ -642,13 +616,13 @@ Kotlin::
[source,kotlin,role="secondary"]
----
@Configuration
class WebClientConfig {
class RestClientConfig {
@Bean
fun webClient(authorizedClientManager: OAuth2AuthorizedClientManager): WebClient {
val filter = ServletOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager)
return WebClient.builder()
.apply(filter.oauth2Configuration())
fun restClient(authorizedClientManager: OAuth2AuthorizedClientManager): RestClient {
val requestInterceptor = OAuth2ClientHttpRequestInterceptor(authorizedClientManager)
return RestClient.builder()
.requestInterceptor(requestInterceptor)
.build()
}
@@ -656,35 +630,35 @@ class WebClientConfig {
----
=====
This configured `WebClient` can be used as in the following example:
This configured `RestClient` can be used as in the following example:
[[oauth2-client-accessing-protected-resources-example]]
.Use `WebClient` to Access Protected Resources
.Use `RestClient` to Access Protected Resources
[tabs]
=====
Java::
+
[source,java,role="primary"]
----
import static org.springframework.security.oauth2.client.web.reactive.function.client.ServletOAuth2AuthorizedClientExchangeFilterFunction.clientRegistrationId;
import static org.springframework.security.oauth2.client.web.client.RequestAttributeClientRegistrationIdResolver.clientRegistrationId;
@RestController
public class MessagesController {
private final WebClient webClient;
private final RestClient restClient;
public MessagesController(WebClient webClient) {
this.webClient = webClient;
public MessagesController(RestClient restClient) {
this.restClient = restClient;
}
@GetMapping("/messages")
public ResponseEntity<List<Message>> messages() {
return this.webClient.get()
Message[] messages = this.restClient.get()
.uri("http://localhost:8090/messages")
.attributes(clientRegistrationId("my-oauth2-client"))
.retrieve()
.toEntityList(Message.class)
.block();
.body(Message[].class);
return ResponseEntity.ok(Arrays.asList(messages));
}
public record Message(String message) {
@@ -697,19 +671,21 @@ Kotlin::
+
[source,kotlin,role="secondary"]
----
import org.springframework.security.oauth2.client.web.reactive.function.client.ServletOAuth2AuthorizedClientExchangeFilterFunction.clientRegistrationId
import org.springframework.security.oauth2.client.web.client.RequestAttributeClientRegistrationIdResolver.clientRegistrationId
import org.springframework.web.client.body
@RestController
class MessagesController(private val webClient: WebClient) {
class MessagesController(private val restClient: RestClient) {
@GetMapping("/messages")
fun messages(): ResponseEntity<List<Message>> {
return webClient.get()
val messages = restClient.get()
.uri("http://localhost:8090/messages")
.attributes(clientRegistrationId("my-oauth2-client"))
.retrieve()
.toEntityList<Message>()
.block()!!
.body<Array<Message>>()!!
.toList()
return ResponseEntity.ok(messages)
}
data class Message(val message: String)
@@ -815,7 +791,227 @@ Spring Security provides implementations of `OAuth2AuthorizedClientManager` for
Spring Security registers a default `OAuth2AuthorizedClientManager` bean for you when one does not exist.
====
The easiest way to use an `OAuth2AuthorizedClientManager` is via an `ExchangeFilterFunction` that intercepts requests through a `WebClient`.
The easiest way to use an `OAuth2AuthorizedClientManager` is via a `ClientHttpRequestInterceptor` that intercepts requests through a `RestClient`, which is already available when `spring-web` is on the classpath.
The following example uses the default `OAuth2AuthorizedClientManager` to configure a `RestClient` capable of accessing protected resources by placing `Bearer` tokens in the `Authorization` header of each request:
.Configure `RestClient` with `ClientHttpRequestInterceptor`
[tabs]
=====
Java::
+
[source,java,role="primary"]
----
@Configuration
public class RestClientConfig {
@Bean
public RestClient restClient(OAuth2AuthorizedClientManager authorizedClientManager) {
OAuth2ClientHttpRequestInterceptor requestInterceptor =
new OAuth2ClientHttpRequestInterceptor(authorizedClientManager, clientRegistrationIdResolver());
return RestClient.builder()
.requestInterceptor(requestInterceptor)
.build();
}
private static ClientRegistrationIdResolver clientRegistrationIdResolver() {
return (request) -> {
Authentication authentication = SecurityContextHolder.getAuthentication();
return (authentication instanceof OAuth2AuthenticationToken principal)
? principal.getAuthorizedClientRegistrationId()
: null;
};
}
}
----
Kotlin::
+
[source,kotlin,role="secondary"]
----
@Configuration
class RestClientConfig {
@Bean
fun restClient(authorizedClientManager: OAuth2AuthorizedClientManager): RestClient {
val requestInterceptor = OAuth2ClientHttpRequestInterceptor(authorizedClientManager, clientRegistrationIdResolver())
return RestClient.builder()
.requestInterceptor(requestInterceptor)
.build()
}
private fun clientRegistrationIdResolver(): OAuth2ClientHttpRequestInterceptor.ClientRegistrationIdResolver {
return OAuth2ClientHttpRequestInterceptor.ClientRegistrationIdResolver { request ->
val authentication = SecurityContextHolder.getContext().authentication
if (authentication is OAuth2AuthenticationToken) {
authentication.authorizedClientRegistrationId
} else {
null
}
}
}
}
----
=====
This configured `RestClient` can be used as in the following example:
[[oauth2-client-accessing-protected-resources-current-user-example]]
.Use `RestClient` to Access Protected Resources (Current User)
[tabs]
=====
Java::
+
[source,java,role="primary"]
----
@RestController
public class MessagesController {
private final RestClient restClient;
public MessagesController(RestClient restClient) {
this.restClient = restClient;
}
@GetMapping("/messages")
public ResponseEntity<List<Message>> messages() {
Message[] messages = this.restClient.get()
.uri("http://localhost:8090/messages")
.retrieve()
.body(Message[].class);
return ResponseEntity.ok(Arrays.asList(messages));
}
public record Message(String message) {
}
}
----
Kotlin::
+
[source,kotlin,role="secondary"]
----
import org.springframework.web.client.body
@RestController
class MessagesController(private val restClient: RestClient) {
@GetMapping("/messages")
fun messages(): ResponseEntity<List<Message>> {
val messages = restClient.get()
.uri("http://localhost:8090/messages")
.retrieve()
.body<Array<Message>>()!!
.toList()
return ResponseEntity.ok(messages)
}
data class Message(val message: String)
}
----
=====
[NOTE]
====
Unlike the <<oauth2-client-accessing-protected-resources-example,previous example>>, notice that we do not need to tell Spring Security about the `clientRegistrationId` we'd like to use.
This is because it can be derived from the currently logged in user.
====
[[oauth2-client-access-protected-resources-webclient]]
=== Access Protected Resources with `WebClient`
Making requests to a third party API that is protected by OAuth2 is a core use case of OAuth2 Client.
This is accomplished by authorizing a client (represented by the `OAuth2AuthorizedClient` class in Spring Security) and accessing protected resources by placing a `Bearer` token in the `Authorization` header of an outbound request.
The following example configures the application to act as an OAuth2 Client capable of requesting protected resources from a third party API:
.Configure OAuth2 Client
[tabs]
=====
Java::
+
[source,java,role="primary"]
----
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
// ...
.oauth2Client(Customizer.withDefaults());
return http.build();
}
}
----
Kotlin::
+
[source,kotlin,role="secondary"]
----
import org.springframework.security.config.annotation.web.invoke
@Configuration
@EnableWebSecurity
class SecurityConfig {
@Bean
fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
http {
// ...
oauth2Client { }
}
return http.build()
}
}
----
=====
[NOTE]
====
The above example does not provide a way to log users in.
You can use any other login mechanism (such as `formLogin()`).
See the <<oauth2-client-access-protected-resources-current-user,previous section>> for an example combining `oauth2Client()` with `oauth2Login()`.
====
In addition to the above configuration, the application requires at least one `ClientRegistration` to be configured through the use of a `ClientRegistrationRepository` bean.
The following example configures an `InMemoryClientRegistrationRepository` bean using Spring Boot configuration properties:
[source,yaml]
----
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
----
In addition to configuring Spring Security to support OAuth2 Client features, you will also need to decide how you will be accessing protected resources and configure your application accordingly.
Spring Security provides implementations of `OAuth2AuthorizedClientManager` for obtaining access tokens that can be used to access protected resources.
[TIP]
====
Spring Security registers a default `OAuth2AuthorizedClientManager` bean for you when one does not exist.
====
Another way to use an `OAuth2AuthorizedClientManager` is via an `ExchangeFilterFunction` that intercepts requests through a `WebClient`.
To use `WebClient`, you will need to add the `spring-webflux` dependency along with a reactive client implementation:
.Add Spring WebFlux Dependency
@@ -889,14 +1085,15 @@ class WebClientConfig {
This configured `WebClient` can be used as in the following example:
[[oauth2-client-accessing-protected-resources-current-user-example]]
.Use `WebClient` to Access Protected Resources (Current User)
.Use `WebClient` to Access Protected Resources
[tabs]
=====
Java::
+
[source,java,role="primary"]
----
import static org.springframework.security.oauth2.client.web.reactive.function.client.ServletOAuth2AuthorizedClientExchangeFilterFunction.clientRegistrationId;
@RestController
public class MessagesController {
@@ -910,6 +1107,7 @@ public class MessagesController {
public ResponseEntity<List<Message>> messages() {
return this.webClient.get()
.uri("http://localhost:8090/messages")
.attributes(clientRegistrationId("my-oauth2-client"))
.retrieve()
.toEntityList(Message.class)
.block();
@@ -925,6 +1123,8 @@ Kotlin::
+
[source,kotlin,role="secondary"]
----
import org.springframework.security.oauth2.client.web.reactive.function.client.ServletOAuth2AuthorizedClientExchangeFilterFunction.clientRegistrationId
@RestController
class MessagesController(private val webClient: WebClient) {
@@ -932,6 +1132,7 @@ class MessagesController(private val webClient: WebClient) {
fun messages(): ResponseEntity<List<Message>> {
return webClient.get()
.uri("http://localhost:8090/messages")
.attributes(clientRegistrationId("my-oauth2-client"))
.retrieve()
.toEntityList<Message>()
.block()!!
@@ -943,12 +1144,6 @@ class MessagesController(private val webClient: WebClient) {
----
=====
[NOTE]
====
Unlike the <<oauth2-client-accessing-protected-resources-example,previous example>>, notice that we do not need to tell Spring Security about the `clientRegistrationId` we'd like to use.
This is because it can be derived from the currently logged in user.
====
[[oauth2-client-enable-extension-grant-type]]
=== Enable an Extension Grant Type