Add RestClient interceptor
Closes gh-13588
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user