Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -340,7 +340,7 @@ class OAuth2ClientSecurityConfig {
Please refer to the https://tools.ietf.org/html/rfc6749#section-4.1.3[Access Token Request/Response] protocol flow for the Authorization Code grant.
====

The default implementation of `ReactiveOAuth2AccessTokenResponseClient` for the Authorization Code grant is `WebClientReactiveAuthorizationCodeTokenResponseClient`, which uses a `WebClient` for exchanging an authorization code for an access token at the Authorization Servers Token Endpoint.
The default implementation of `ReactiveOAuth2AccessTokenResponseClient` for the Authorization Code grant is `WebClientReactiveAuthorizationCodeTokenResponseClient`, which uses a `WebClient` for exchanging an authorization code for an access token at the Authorization Server's Token Endpoint.

:section-id: authorization-code
:grant-type: Authorization Code
Expand Down Expand Up @@ -432,7 +432,7 @@ Please refer to the OAuth 2.0 Authorization Framework for further details on the
Please refer to the https://tools.ietf.org/html/rfc6749#section-6[Access Token Request/Response] protocol flow for the Refresh Token grant.
====

The default implementation of `ReactiveOAuth2AccessTokenResponseClient` for the Refresh Token grant is `WebClientReactiveRefreshTokenTokenResponseClient`, which uses a `WebClient` when refreshing an access token at the Authorization Servers Token Endpoint.
The default implementation of `ReactiveOAuth2AccessTokenResponseClient` for the Refresh Token grant is `WebClientReactiveRefreshTokenTokenResponseClient`, which uses a `WebClient` when refreshing an access token at the Authorization Server's Token Endpoint.

:section-id: refresh-token
:grant-type: Refresh Token
Expand Down Expand Up @@ -512,7 +512,7 @@ Please refer to the OAuth 2.0 Authorization Framework for further details on the
Please refer to the https://tools.ietf.org/html/rfc6749#section-4.4.2[Access Token Request/Response] protocol flow for the Client Credentials grant.
====

The default implementation of `ReactiveOAuth2AccessTokenResponseClient` for the Client Credentials grant is `WebClientReactiveClientCredentialsTokenResponseClient`, which uses a `WebClient` when requesting an access token at the Authorization Servers Token Endpoint.
The default implementation of `ReactiveOAuth2AccessTokenResponseClient` for the Client Credentials grant is `WebClientReactiveClientCredentialsTokenResponseClient`, which uses a `WebClient` when requesting an access token at the Authorization Server's Token Endpoint.

:section-id: client-credentials
:grant-type: Client Credentials
Expand Down Expand Up @@ -698,6 +698,31 @@ class OAuth2ClientController {
If not provided, it will be obtained from the https://projectreactor.io/docs/core/release/reference/#context[Reactor's Context] via the key `ServerWebExchange.class`.
====

[[oauth2-client-client-credentials-application-scoped]]
=== Use the Client Credentials Grant for Application-Scoped Access Tokens

When making requests that are not associated with a specific user (e.g. background jobs, batch processes, or scheduled tasks), use `AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager` instead of `DefaultReactiveOAuth2AuthorizedClientManager`.
Unlike `DefaultReactiveOAuth2AuthorizedClientManager`, `AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager` operates outside of a `ServerWebExchange` context, making it suitable for application-scoped access tokens.

The following example shows how to configure a `WebClient` for application-scoped access tokens using the Client Credentials grant:

.WebClient with Application-Scoped Access Tokens
[tabs]
======
Java::
+
[source,java,role="primary",indent=0]
----
include::{tests-dir}/reactive/oauth2/webclient/ApplicationScopedAccessTokenConfiguration.java[tag=webclient-client-credentials]
----
======

[NOTE]
====
`AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager` uses a `ReactiveOAuth2AuthorizedClientService` to persist authorized clients, rather than a `ServerOAuth2AuthorizedClientRepository`.
This makes it suitable for use cases where there is no active `ServerWebExchange`, such as background tasks or scheduled jobs.
====

[[oauth2-client-jwt-bearer]]
== JWT Bearer

Expand All @@ -714,7 +739,7 @@ Please refer to JSON Web Token (JWT) Profile for OAuth 2.0 Client Authentication
Please refer to the https://datatracker.ietf.org/doc/html/rfc7523#section-2.1[Access Token Request/Response] protocol flow for the JWT Bearer grant.
====

The default implementation of `ReactiveOAuth2AccessTokenResponseClient` for the JWT Bearer grant is `WebClientReactiveJwtBearerTokenResponseClient`, which uses a `WebClient` when requesting an access token at the Authorization Servers Token Endpoint.
The default implementation of `ReactiveOAuth2AccessTokenResponseClient` for the JWT Bearer grant is `WebClientReactiveJwtBearerTokenResponseClient`, which uses a `WebClient` when requesting an access token at the Authorization Server's Token Endpoint.

:section-id: jwt-bearer
:grant-type: JWT Bearer
Expand Down Expand Up @@ -922,7 +947,7 @@ Please refer to OAuth 2.0 Token Exchange for further details on the https://data
Please refer to the https://datatracker.ietf.org/doc/html/rfc8693#section-2[Token Exchange Request and Response] protocol flow for the Token Exchange grant.
====

The default implementation of `ReactiveOAuth2AccessTokenResponseClient` for the Token Exchange grant is `WebClientReactiveTokenExchangeTokenResponseClient`, which uses a `WebClient` when requesting an access token at the Authorization Servers Token Endpoint.
The default implementation of `ReactiveOAuth2AccessTokenResponseClient` for the Token Exchange grant is `WebClientReactiveTokenExchangeTokenResponseClient`, which uses a `WebClient` when requesting an access token at the Authorization Server's Token Endpoint.

:section-id: token-exchange
:grant-type: Token Exchange
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.springframework.security.docs.reactive.oauth2.webclient;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.oauth2.client.AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager;
import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientManager;
import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientProviderBuilder;
import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientService;
import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository;
import org.springframework.security.oauth2.client.web.reactive.function.client.ServerOAuth2AuthorizedClientExchangeFilterFunction;
import org.springframework.web.reactive.function.client.WebClient;

@Configuration
public class ApplicationScopedAccessTokenConfiguration {

// tag::webclient-client-credentials[]
@Bean
ReactiveOAuth2AuthorizedClientManager authorizedClientManager(
ReactiveClientRegistrationRepository clientRegistrationRepository,
ReactiveOAuth2AuthorizedClientService authorizedClientService) {

var authorizedClientProvider = ReactiveOAuth2AuthorizedClientProviderBuilder.builder()
.clientCredentials()
.build();

var authorizedClientManager = new AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager(
clientRegistrationRepository, authorizedClientService);
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);

return authorizedClientManager;
}

@Bean
WebClient webClient(ReactiveOAuth2AuthorizedClientManager authorizedClientManager) {
var oauth2Client = new ServerOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager);
oauth2Client.setDefaultClientRegistrationId("my-client");

return WebClient.builder()
.filter(oauth2Client)
.build();
}
// end::webclient-client-credentials[]

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/*
* Copyright 2004-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.springframework.security.docs.reactive.oauth2.webclient;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mockito;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.test.SpringTestContext;
import org.springframework.security.config.test.SpringTestContextExtension;
import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientService;
import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository;
import org.springframework.web.reactive.function.client.WebClient;

import static org.assertj.core.api.Assertions.assertThat;

/**
* Tests {@link ApplicationScopedAccessTokenConfiguration}.
*/
@ExtendWith(SpringTestContextExtension.class)
public class ApplicationScopedAccessTokenTests {

public final SpringTestContext spring = new SpringTestContext(this);

@Autowired
WebClient webClient;

@Test
void webClientWhenClientCredentialsThenConfigured() {
this.spring.register(TestConfig.class, ApplicationScopedAccessTokenConfiguration.class).autowire();
assertThat(this.webClient).isNotNull();
}

@Configuration
static class TestConfig {

@Bean
ReactiveClientRegistrationRepository clientRegistrationRepository() {
return Mockito.mock(ReactiveClientRegistrationRepository.class);
}

@Bean
ReactiveOAuth2AuthorizedClientService authorizedClientService() {
return Mockito.mock(ReactiveOAuth2AuthorizedClientService.class);
}

}

}