mirror of
https://github.com/spring-projects/spring-security.git
synced 2025-06-25 21:42:17 +00:00
Add OAuth Support for HTTP Interface Client
Closes gh-16858
This commit is contained in:
parent
502b0b7f95
commit
b2325e4176
@ -19,6 +19,8 @@
|
||||
*** xref:features/exploits/headers.adoc[HTTP Headers]
|
||||
*** xref:features/exploits/http.adoc[HTTP Requests]
|
||||
** xref:features/integrations/index.adoc[Integrations]
|
||||
*** REST Client
|
||||
**** xref:features/integrations/rest/http-interface.adoc[HTTP Interface Integration]
|
||||
*** xref:features/integrations/cryptography.adoc[Cryptography]
|
||||
*** xref:features/integrations/data.adoc[Spring Data]
|
||||
*** xref:features/integrations/concurrency.adoc[Java's Concurrency APIs]
|
||||
|
@ -0,0 +1,66 @@
|
||||
= HTTP Interface Integration
|
||||
|
||||
Spring Security's OAuth Support can integrate with `RestClient` and `WebClient` {spring-framework-reference-url}/integration/rest-clients.html[HTTP Interface based REST Clients].
|
||||
|
||||
|
||||
[[configuration]]
|
||||
== Configuration
|
||||
After xref:features/integrations/rest/http-interface.adoc#configuration-restclient[RestClient] or xref:features/integrations/rest/http-interface.adoc#configuration-webclient[WebClient] specific configuration, usage of xref:features/integrations/rest/http-interface.adoc[] only requires adding a xref:features/integrations/rest/http-interface.adoc#client-registration-id[`@ClientRegistrationId`] to methods that require OAuth.
|
||||
|
||||
Since the presense of xref:features/integrations/rest/http-interface.adoc#client-registration-id[`@ClientRegistrationId`] determines if and how the OAuth token will be resolved, it is safe to add Spring Security's OAuth support any configuration.
|
||||
|
||||
[[configuration-restclient]]
|
||||
=== RestClient Configuration
|
||||
|
||||
Spring Security's OAuth Support can integrate with {spring-framework-reference-url}/integration/rest-clients.html[HTTP Interface based REST Clients] backed by RestClient.
|
||||
The first step is to xref:servlet/oauth2/client/core.adoc#oauth2Client-authorized-manager-provider[create an `OAuthAuthorizedClientManager` Bean].
|
||||
|
||||
Next you must configure `HttpServiceProxyFactory` and `RestClient` to be aware of xref:./http-interface.adoc#client-registration-id[@ClientRegistrationId]
|
||||
To simplify this configuration, use javadoc:org.springframework.security.oauth2.client.web.client.support.OAuth2RestClientHttpServiceGroupConfigurer[].
|
||||
|
||||
include-code::./RestClientHttpInterfaceIntegrationConfiguration[tag=config,indent=0]
|
||||
|
||||
The configuration:
|
||||
|
||||
- Adds xref:features/integrations/rest/http-interface.adoc#client-registration-id-processor[`ClientRegistrationIdProcessor`] to {spring-framework-reference-url}/integration/rest-clients.html#rest-http-interface[`HttpServiceProxyFactory`]
|
||||
- Adds xref:servlet/oauth2/client/authorized-clients.adoc#oauth2-client-rest-client[`OAuth2ClientHttpRequestInterceptor`] to the `RestClient`
|
||||
|
||||
[[configuration-webclient]]
|
||||
=== WebClient Configuration
|
||||
|
||||
Spring Security's OAuth Support can integrate with {spring-framework-reference-url}/integration/rest-clients.html[HTTP Interface based REST Clients] backed by `WebClient`.
|
||||
The first step is to xref:reactive/oauth2/client/core.adoc#oauth2Client-authorized-manager-provider[create an `ReactiveOAuthAuthorizedClientManager` Bean].
|
||||
|
||||
Next you must configure `HttpServiceProxyFactory` and `WebRestClient` to be aware of xref:./http-interface.adoc#client-registration-id[@ClientRegistrationId]
|
||||
To simplify this configuration, use javadoc:org.springframework.security.oauth2.client.web.reactive.function.client.support.OAuth2WebClientHttpServiceGroupConfigurer[].
|
||||
|
||||
include-code::./ServerWebClientHttpInterfaceIntegrationConfiguration[tag=config,indent=0]
|
||||
|
||||
The configuration:
|
||||
|
||||
- Adds xref:features/integrations/rest/http-interface.adoc#client-registration-id-processor[`ClientRegistrationIdProcessor`] to {spring-framework-reference-url}/integration/rest-clients.html#rest-http-interface[`HttpServiceProxyFactory`]
|
||||
- Adds xref:reactive/oauth2/client/authorized-clients.adoc#oauth2-client-web-client[`ServerOAuth2AuthorizedClientExchangeFilterFunction`] to the `WebClient`
|
||||
|
||||
|
||||
[[client-registration-id]]
|
||||
== @ClientRegistrationId
|
||||
|
||||
You can add the javadoc:org.springframework.security.oauth2.client.annotation.ClientRegistrationId[] on the HTTP Interface to specify which javadoc:org.springframework.security.oauth2.client.registration.ClientRegistration[] to use.
|
||||
|
||||
include-code::./UserService[tag=getAuthenticatedUser]
|
||||
|
||||
The xref:features/integrations/rest/http-interface.adoc#client-registration-id[`@ClientRegistrationId`] will be processed by xref:features/integrations/rest/http-interface.adoc#client-registration-id-processor[`ClientRegistrationIdProcessor`]
|
||||
|
||||
[[client-registration-id-processor]]
|
||||
== `ClientRegistrationIdProcessor`
|
||||
|
||||
The xref:features/integrations/rest/http-interface.adoc#configuration[configured] javadoc:org.springframework.security.oauth2.client.web.client.ClientRegistrationIdProcessor[] will:
|
||||
|
||||
- Automatically invoke javadoc:org.springframework.security.oauth2.client.web.ClientAttributes#clientRegistrationId(java.lang.String)[] for each xref:features/integrations/rest/http-interface.adoc#client-registration-id[`@ClientRegistrationId`].
|
||||
- This adds the javadoc:org.springframework.security.oauth2.client.registration.ClientRegistration#getId()[] to the attributes
|
||||
|
||||
The `id` is then processed by:
|
||||
|
||||
- `OAuth2ClientHttpRequestInterceptor` for xref:servlet/oauth2/client/authorized-clients.adoc#oauth2-client-rest-client[RestClient Integration]
|
||||
- xref:servlet/oauth2/client/authorized-clients.adoc#oauth2-client-web-client[`ServletOAuth2AuthorizedClientExchangeFilterFunction`] (servlets) or xref:servlet/oauth2/client/authorized-clients.adoc#oauth2-client-web-client[`ServerOAuth2AuthorizedClientExchangeFilterFunction`] (reactive environments) for `WebClient`.
|
||||
|
@ -495,6 +495,11 @@ class RestClientConfig {
|
||||
----
|
||||
=====
|
||||
|
||||
[[oauth2-client-rest-client-interface]]
|
||||
=== HTTP Interface Integration
|
||||
|
||||
Spring Security's OAuth support integrates with xref:features/integrations/rest/http-interface.adoc[].
|
||||
|
||||
[[oauth2-client-web-client]]
|
||||
== [[oauth2Client-webclient-servlet]]WebClient Integration for Servlet Environments
|
||||
|
||||
|
@ -7,3 +7,4 @@ Below are the highlights of the release, or you can view https://github.com/spri
|
||||
== Web
|
||||
|
||||
* Added javadoc:org.springframework.security.web.authentication.preauth.x509.SubjectX500PrincipalExtractor[]
|
||||
* Added OAuth2 Support for xref:features/integrations/rest/http-interface.adoc[HTTP Interface Integration]
|
||||
|
@ -39,6 +39,8 @@ dependencies {
|
||||
testImplementation project(':spring-security-config')
|
||||
testImplementation project(path : ':spring-security-config', configuration : 'tests')
|
||||
testImplementation project(':spring-security-test')
|
||||
testImplementation project(':spring-security-oauth2-client')
|
||||
testImplementation 'com.squareup.okhttp3:mockwebserver'
|
||||
testImplementation 'com.unboundid:unboundid-ldapsdk'
|
||||
testImplementation libs.webauthn4j.core
|
||||
testImplementation 'org.jetbrains.kotlin:kotlin-reflect'
|
||||
@ -49,6 +51,7 @@ dependencies {
|
||||
|
||||
testImplementation 'org.springframework:spring-webmvc'
|
||||
testImplementation 'jakarta.servlet:jakarta.servlet-api'
|
||||
testImplementation 'io.mockk:mockk'
|
||||
testImplementation "org.junit.jupiter:junit-jupiter-api"
|
||||
testImplementation "org.junit.jupiter:junit-jupiter-params"
|
||||
testImplementation "org.junit.jupiter:junit-jupiter-engine"
|
||||
|
@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright 2002-2025 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 clients 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.features.integrations.rest.clientregistrationid;
|
||||
|
||||
/**
|
||||
* A user.
|
||||
* @param login
|
||||
* @param id
|
||||
* @param name
|
||||
* @author Rob Winch
|
||||
* @see UserService
|
||||
*/
|
||||
public record User(String login, int id, String name) {
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright 2002-2025 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 clients 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.features.integrations.rest.clientregistrationid;
|
||||
|
||||
import org.springframework.security.oauth2.client.annotation.ClientRegistrationId;
|
||||
import org.springframework.web.service.annotation.GetExchange;
|
||||
import org.springframework.web.service.annotation.HttpExchange;
|
||||
|
||||
/**
|
||||
* Demonstrates a service for {@link ClientRegistrationId} and HTTP Interface clients.
|
||||
* @author Rob Winch
|
||||
*/
|
||||
@HttpExchange
|
||||
public interface UserService {
|
||||
|
||||
// tag::getAuthenticatedUser[]
|
||||
@GetExchange("/user")
|
||||
@ClientRegistrationId("github")
|
||||
User getAuthenticatedUser();
|
||||
// end::getAuthenticatedUser[]
|
||||
|
||||
}
|
@ -0,0 +1,67 @@
|
||||
/*
|
||||
* Copyright 2002-2025 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 clients 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.features.integrations.rest.configurationrestclient;
|
||||
|
||||
import okhttp3.mockwebserver.MockWebServer;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.docs.features.integrations.rest.clientregistrationid.UserService;
|
||||
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientManager;
|
||||
import org.springframework.security.oauth2.client.web.client.support.OAuth2RestClientHttpServiceGroupConfigurer;
|
||||
import org.springframework.web.client.support.RestClientHttpServiceGroupConfigurer;
|
||||
import org.springframework.web.service.registry.ImportHttpServices;
|
||||
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* Documentation for {@link OAuth2RestClientHttpServiceGroupConfigurer}.
|
||||
* @author Rob Winch
|
||||
*/
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@ImportHttpServices(types = UserService.class)
|
||||
public class RestClientHttpInterfaceIntegrationConfiguration {
|
||||
|
||||
// tag::config[]
|
||||
@Bean
|
||||
OAuth2RestClientHttpServiceGroupConfigurer securityConfigurer(
|
||||
OAuth2AuthorizedClientManager manager) {
|
||||
return OAuth2RestClientHttpServiceGroupConfigurer.from(manager);
|
||||
}
|
||||
// end::config[]
|
||||
|
||||
@Bean
|
||||
OAuth2AuthorizedClientManager authorizedClientManager() {
|
||||
return mock(OAuth2AuthorizedClientManager.class);
|
||||
}
|
||||
|
||||
@Bean
|
||||
RestClientHttpServiceGroupConfigurer groupConfigurer(MockWebServer server) {
|
||||
return groups -> {
|
||||
|
||||
groups
|
||||
.forEachClient((group, builder) -> builder
|
||||
.baseUrl(server.url("").toString())
|
||||
.defaultHeader("Accept", "application/vnd.github.v3+json"));
|
||||
};
|
||||
}
|
||||
|
||||
@Bean
|
||||
MockWebServer mockServer() {
|
||||
return new MockWebServer();
|
||||
}
|
||||
}
|
@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Copyright 2002-2025 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 clients 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.features.integrations.rest.configurationrestclient;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
|
||||
import okhttp3.mockwebserver.MockResponse;
|
||||
import okhttp3.mockwebserver.MockWebServer;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.security.config.oauth2.client.CommonOAuth2Provider;
|
||||
import org.springframework.security.docs.features.integrations.rest.clientregistrationid.UserService;
|
||||
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
|
||||
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientManager;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||
import org.springframework.security.oauth2.core.OAuth2AccessToken;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
|
||||
/**
|
||||
* Tests RestClient configuration for HTTP Interface clients.
|
||||
* @author Rob Winch
|
||||
*/
|
||||
@ExtendWith(SpringExtension.class)
|
||||
@ContextConfiguration(classes = RestClientHttpInterfaceIntegrationConfiguration.class)
|
||||
class RestClientHttpInterfaceIntegrationConfigurationTests {
|
||||
|
||||
@Test
|
||||
void getAuthenticatedUser(@Autowired MockWebServer webServer, @Autowired OAuth2AuthorizedClientManager authorizedClients, @Autowired UserService users)
|
||||
throws InterruptedException {
|
||||
ClientRegistration registration = CommonOAuth2Provider.GITHUB.getBuilder("github").clientId("github").build();
|
||||
|
||||
Instant issuedAt = Instant.now();
|
||||
Instant expiresAt = issuedAt.plus(Duration.ofMinutes(5));
|
||||
OAuth2AccessToken token = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER, "1234",
|
||||
issuedAt, expiresAt);
|
||||
OAuth2AuthorizedClient result = new OAuth2AuthorizedClient(registration, "rob", token);
|
||||
given(authorizedClients.authorize(any())).willReturn(result);
|
||||
|
||||
webServer.enqueue(new MockResponse().addHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE).setBody(
|
||||
"""
|
||||
{"login": "rob_winch", "id": 1234, "name": "Rob Winch" }
|
||||
"""));
|
||||
|
||||
users.getAuthenticatedUser();
|
||||
|
||||
assertThat(webServer.takeRequest().getHeader(HttpHeaders.AUTHORIZATION)).isEqualTo("Bearer " + token.getTokenValue());
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,75 @@
|
||||
/*
|
||||
* Copyright 2002-2025 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 clients 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.features.integrations.rest.configurationwebclient;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
|
||||
import okhttp3.mockwebserver.MockResponse;
|
||||
import okhttp3.mockwebserver.MockWebServer;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.security.config.oauth2.client.CommonOAuth2Provider;
|
||||
import org.springframework.security.docs.features.integrations.rest.clientregistrationid.UserService;
|
||||
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
|
||||
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientManager;
|
||||
import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientManager;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||
import org.springframework.security.oauth2.core.OAuth2AccessToken;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
|
||||
/**
|
||||
* Demonstrates configuring RestClient with interface based proxy clients.
|
||||
* @author Rob Winch
|
||||
*/
|
||||
@ExtendWith(SpringExtension.class)
|
||||
@ContextConfiguration(classes = ServerWebClientHttpInterfaceIntegrationConfiguration.class)
|
||||
class ServerRestClientHttpInterfaceIntegrationConfigurationTests {
|
||||
|
||||
@Test
|
||||
void getAuthenticatedUser(@Autowired MockWebServer webServer, @Autowired ReactiveOAuth2AuthorizedClientManager authorizedClients, @Autowired UserService users)
|
||||
throws InterruptedException {
|
||||
ClientRegistration registration = CommonOAuth2Provider.GITHUB.getBuilder("github").clientId("github").build();
|
||||
|
||||
Instant issuedAt = Instant.now();
|
||||
Instant expiresAt = issuedAt.plus(Duration.ofMinutes(5));
|
||||
OAuth2AccessToken token = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER, "1234",
|
||||
issuedAt, expiresAt);
|
||||
OAuth2AuthorizedClient result = new OAuth2AuthorizedClient(registration, "rob", token);
|
||||
given(authorizedClients.authorize(any())).willReturn(Mono.just(result));
|
||||
|
||||
webServer.enqueue(new MockResponse().addHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE).setBody(
|
||||
"""
|
||||
{"login": "rob_winch", "id": 1234, "name": "Rob Winch" }
|
||||
"""));
|
||||
|
||||
users.getAuthenticatedUser();
|
||||
|
||||
assertThat(webServer.takeRequest().getHeader(HttpHeaders.AUTHORIZATION)).isEqualTo("Bearer " + token.getTokenValue());
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Copyright 2002-2025 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 clients 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.features.integrations.rest.configurationwebclient;
|
||||
|
||||
import okhttp3.mockwebserver.MockWebServer;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.docs.features.integrations.rest.clientregistrationid.UserService;
|
||||
import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientManager;
|
||||
import org.springframework.security.oauth2.client.web.client.support.OAuth2RestClientHttpServiceGroupConfigurer;
|
||||
import org.springframework.security.oauth2.client.web.reactive.function.client.support.OAuth2WebClientHttpServiceGroupConfigurer;
|
||||
import org.springframework.web.reactive.function.client.support.WebClientHttpServiceGroupConfigurer;
|
||||
import org.springframework.web.service.registry.HttpServiceGroup;
|
||||
import org.springframework.web.service.registry.ImportHttpServices;
|
||||
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* Documentation for {@link OAuth2RestClientHttpServiceGroupConfigurer}.
|
||||
* @author Rob Winch
|
||||
*/
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@ImportHttpServices(types = UserService.class, clientType = HttpServiceGroup.ClientType.WEB_CLIENT)
|
||||
public class ServerWebClientHttpInterfaceIntegrationConfiguration {
|
||||
|
||||
// tag::config[]
|
||||
@Bean
|
||||
OAuth2WebClientHttpServiceGroupConfigurer securityConfigurer(
|
||||
ReactiveOAuth2AuthorizedClientManager manager) {
|
||||
return OAuth2WebClientHttpServiceGroupConfigurer.from(manager);
|
||||
}
|
||||
// end::config[]
|
||||
|
||||
@Bean
|
||||
ReactiveOAuth2AuthorizedClientManager authorizedClientManager() {
|
||||
return mock(ReactiveOAuth2AuthorizedClientManager.class);
|
||||
}
|
||||
|
||||
@Bean
|
||||
WebClientHttpServiceGroupConfigurer groupConfigurer(MockWebServer server) {
|
||||
return groups -> {
|
||||
String baseUrl = server.url("").toString();
|
||||
groups
|
||||
.forEachClient((group, builder) -> builder
|
||||
.baseUrl(baseUrl)
|
||||
.defaultHeader("Accept", "application/vnd.github.v3+json"));
|
||||
};
|
||||
}
|
||||
|
||||
@Bean
|
||||
MockWebServer mockServer() {
|
||||
return new MockWebServer();
|
||||
}
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright 2002-2025 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 clients 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.kt.docs.features.integrations.rest.clientregistrationid
|
||||
|
||||
|
||||
/**
|
||||
* A user.
|
||||
* @param login
|
||||
* @param id
|
||||
* @param name
|
||||
* @author Rob Winch
|
||||
* @see UserService
|
||||
*/
|
||||
@JvmRecord
|
||||
data class User(val login: String, val id: Int, val name: String)
|
@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright 2002-2025 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 clients 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.kt.docs.features.integrations.rest.clientregistrationid
|
||||
|
||||
import org.springframework.security.oauth2.client.annotation.ClientRegistrationId
|
||||
import org.springframework.web.service.annotation.GetExchange
|
||||
import org.springframework.web.service.annotation.HttpExchange
|
||||
|
||||
/**
|
||||
* Demonstrates a service for {@link ClientRegistrationId} and HTTP Interface clients.
|
||||
* @author Rob Winch
|
||||
*/
|
||||
@HttpExchange
|
||||
interface UserService {
|
||||
|
||||
// tag::getAuthenticatedUser[]
|
||||
@GetExchange("/user")
|
||||
@ClientRegistrationId("github")
|
||||
fun getAuthenticatedUser() : User
|
||||
// end::getAuthenticatedUser[]
|
||||
}
|
@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Copyright 2002-2025 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 clients 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.kt.docs.features.integrations.rest.configurationrestclient
|
||||
|
||||
import okhttp3.mockwebserver.MockWebServer
|
||||
import org.mockito.Mockito
|
||||
import org.springframework.context.annotation.Bean
|
||||
import org.springframework.context.annotation.Configuration
|
||||
import org.springframework.security.kt.docs.features.integrations.rest.clientregistrationid.UserService
|
||||
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientManager
|
||||
import org.springframework.security.oauth2.client.web.client.support.OAuth2RestClientHttpServiceGroupConfigurer
|
||||
import org.springframework.web.client.RestClient
|
||||
import org.springframework.web.client.support.RestClientHttpServiceGroupConfigurer
|
||||
import org.springframework.web.service.registry.HttpServiceGroup
|
||||
import org.springframework.web.service.registry.HttpServiceGroupConfigurer
|
||||
import org.springframework.web.service.registry.HttpServiceGroupConfigurer.ClientCallback
|
||||
import org.springframework.web.service.registry.ImportHttpServices
|
||||
|
||||
/**
|
||||
* Documentation for [OAuth2RestClientHttpServiceGroupConfigurer].
|
||||
* @author Rob Winch
|
||||
*/
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@ImportHttpServices(types = [UserService::class])
|
||||
class RestClientHttpInterfaceIntegrationConfiguration {
|
||||
// tag::config[]
|
||||
@Bean
|
||||
fun securityConfigurer(manager: OAuth2AuthorizedClientManager): OAuth2RestClientHttpServiceGroupConfigurer {
|
||||
return OAuth2RestClientHttpServiceGroupConfigurer.from(manager)
|
||||
}
|
||||
// end::config[]
|
||||
|
||||
@Bean
|
||||
fun authorizedClientManager(): OAuth2AuthorizedClientManager? {
|
||||
return Mockito.mock<OAuth2AuthorizedClientManager?>(OAuth2AuthorizedClientManager::class.java)
|
||||
}
|
||||
|
||||
@Bean
|
||||
fun groupConfigurer(server: MockWebServer): RestClientHttpServiceGroupConfigurer {
|
||||
return RestClientHttpServiceGroupConfigurer { groups: HttpServiceGroupConfigurer.Groups<RestClient.Builder> ->
|
||||
groups.forEachClient(ClientCallback { group: HttpServiceGroup, builder: RestClient.Builder ->
|
||||
builder
|
||||
.baseUrl(server.url("").toString())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@Bean
|
||||
fun mockServer(): MockWebServer {
|
||||
return MockWebServer()
|
||||
}
|
||||
}
|
@ -0,0 +1,75 @@
|
||||
/*
|
||||
* Copyright 2002-2025 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 clients 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.kt.docs.features.integrations.rest.configurationrestclient
|
||||
|
||||
import io.mockk.every
|
||||
import io.mockk.mockkObject
|
||||
import okhttp3.mockwebserver.MockResponse
|
||||
import okhttp3.mockwebserver.MockWebServer
|
||||
import org.assertj.core.api.Assertions
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.extension.ExtendWith
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.http.HttpHeaders
|
||||
import org.springframework.http.MediaType
|
||||
import org.springframework.security.config.oauth2.client.CommonOAuth2Provider
|
||||
import org.springframework.security.kt.docs.features.integrations.rest.clientregistrationid.UserService
|
||||
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient
|
||||
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientManager
|
||||
import org.springframework.security.oauth2.core.OAuth2AccessToken
|
||||
import org.springframework.test.context.ContextConfiguration
|
||||
import org.springframework.test.context.junit.jupiter.SpringExtension
|
||||
import java.time.Duration
|
||||
import java.time.Instant
|
||||
|
||||
@ExtendWith(SpringExtension::class)
|
||||
@ContextConfiguration(classes = [RestClientHttpInterfaceIntegrationConfiguration::class])
|
||||
internal class RestClientHttpInterfaceIntegrationConfigurationTests {
|
||||
@Test
|
||||
fun getAuthenticatedUser(
|
||||
@Autowired webServer: MockWebServer,
|
||||
@Autowired authorizedClients: OAuth2AuthorizedClientManager,
|
||||
@Autowired users: UserService
|
||||
) {
|
||||
val registration = CommonOAuth2Provider.GITHUB.getBuilder("github").clientId("github").build()
|
||||
|
||||
val issuedAt = Instant.now()
|
||||
val expiresAt = issuedAt.plus(Duration.ofMinutes(5))
|
||||
val token = OAuth2AccessToken(
|
||||
OAuth2AccessToken.TokenType.BEARER, "1234",
|
||||
issuedAt, expiresAt
|
||||
)
|
||||
val result = OAuth2AuthorizedClient(registration, "rob", token)
|
||||
mockkObject(authorizedClients)
|
||||
every {
|
||||
authorizedClients.authorize(any())
|
||||
} returns result
|
||||
|
||||
webServer.enqueue(
|
||||
MockResponse().addHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE).setBody(
|
||||
"""
|
||||
{"login": "rob_winch", "id": 1234, "name": "Rob Winch" }
|
||||
""".trimIndent()
|
||||
)
|
||||
)
|
||||
|
||||
users.getAuthenticatedUser()
|
||||
|
||||
Assertions.assertThat(webServer.takeRequest().getHeader(HttpHeaders.AUTHORIZATION))
|
||||
.isEqualTo("Bearer " + token.getTokenValue())
|
||||
}
|
||||
}
|
@ -0,0 +1,78 @@
|
||||
/*
|
||||
* Copyright 2002-2025 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 clients 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.kt.docs.features.integrations.rest.configurationwebclient
|
||||
|
||||
import io.mockk.every
|
||||
import io.mockk.mockkObject
|
||||
import okhttp3.mockwebserver.MockResponse
|
||||
import okhttp3.mockwebserver.MockWebServer
|
||||
import org.assertj.core.api.Assertions
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.extension.ExtendWith
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.http.HttpHeaders
|
||||
import org.springframework.http.MediaType
|
||||
import org.springframework.security.config.oauth2.client.CommonOAuth2Provider
|
||||
import org.springframework.security.kt.docs.features.integrations.rest.clientregistrationid.UserService
|
||||
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient
|
||||
import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientManager
|
||||
import org.springframework.security.oauth2.core.OAuth2AccessToken
|
||||
import org.springframework.test.context.ContextConfiguration
|
||||
import org.springframework.test.context.junit.jupiter.SpringExtension
|
||||
import reactor.core.publisher.Mono
|
||||
import java.time.Duration
|
||||
import java.time.Instant
|
||||
|
||||
@ExtendWith(SpringExtension::class)
|
||||
@ContextConfiguration(classes = [ServerWebClientHttpInterfaceIntegrationConfiguration::class])
|
||||
internal class ServerRestClientHttpInterfaceIntegrationConfigurationTests {
|
||||
@Test
|
||||
@Throws(InterruptedException::class)
|
||||
fun getAuthenticatedUser(
|
||||
@Autowired webServer: MockWebServer,
|
||||
@Autowired authorizedClients: ReactiveOAuth2AuthorizedClientManager,
|
||||
@Autowired users: UserService
|
||||
) {
|
||||
val registration = CommonOAuth2Provider.GITHUB.getBuilder("github").clientId("github").build()
|
||||
|
||||
val issuedAt = Instant.now()
|
||||
val expiresAt = issuedAt.plus(Duration.ofMinutes(5))
|
||||
val token = OAuth2AccessToken(
|
||||
OAuth2AccessToken.TokenType.BEARER, "1234",
|
||||
issuedAt, expiresAt
|
||||
)
|
||||
val result = OAuth2AuthorizedClient(registration, "rob", token)
|
||||
mockkObject(authorizedClients)
|
||||
every {
|
||||
authorizedClients.authorize(any())
|
||||
} returns Mono.just(result)
|
||||
|
||||
webServer.enqueue(
|
||||
MockResponse().addHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE).setBody(
|
||||
"""
|
||||
{"login": "rob_winch", "id": 1234, "name": "Rob Winch" }
|
||||
|
||||
""".trimIndent()
|
||||
)
|
||||
)
|
||||
|
||||
users.getAuthenticatedUser()
|
||||
|
||||
Assertions.assertThat(webServer.takeRequest().getHeader(HttpHeaders.AUTHORIZATION))
|
||||
.isEqualTo("Bearer " + token.getTokenValue())
|
||||
}
|
||||
}
|
@ -0,0 +1,72 @@
|
||||
/*
|
||||
* Copyright 2002-2025 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 clients 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.kt.docs.features.integrations.rest.configurationwebclient
|
||||
|
||||
import okhttp3.mockwebserver.MockWebServer
|
||||
import org.mockito.Mockito
|
||||
import org.springframework.context.annotation.Bean
|
||||
import org.springframework.context.annotation.Configuration
|
||||
import org.springframework.security.kt.docs.features.integrations.rest.clientregistrationid.UserService
|
||||
import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientManager
|
||||
import org.springframework.security.oauth2.client.web.client.support.OAuth2RestClientHttpServiceGroupConfigurer
|
||||
import org.springframework.security.oauth2.client.web.reactive.function.client.support.OAuth2WebClientHttpServiceGroupConfigurer
|
||||
import org.springframework.web.reactive.function.client.WebClient
|
||||
import org.springframework.web.reactive.function.client.support.WebClientHttpServiceGroupConfigurer
|
||||
import org.springframework.web.service.registry.HttpServiceGroup
|
||||
import org.springframework.web.service.registry.HttpServiceGroupConfigurer
|
||||
import org.springframework.web.service.registry.HttpServiceGroupConfigurer.ClientCallback
|
||||
import org.springframework.web.service.registry.ImportHttpServices
|
||||
|
||||
/**
|
||||
* Documentation for [OAuth2RestClientHttpServiceGroupConfigurer].
|
||||
* @author Rob Winch
|
||||
*/
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@ImportHttpServices(types = [UserService::class], clientType = HttpServiceGroup.ClientType.WEB_CLIENT)
|
||||
class ServerWebClientHttpInterfaceIntegrationConfiguration {
|
||||
// tag::config[]
|
||||
@Bean
|
||||
fun securityConfigurer(
|
||||
manager: ReactiveOAuth2AuthorizedClientManager?
|
||||
): OAuth2WebClientHttpServiceGroupConfigurer {
|
||||
return OAuth2WebClientHttpServiceGroupConfigurer.from(manager)
|
||||
}
|
||||
|
||||
// end::config[]
|
||||
@Bean
|
||||
fun authorizedClientManager(): ReactiveOAuth2AuthorizedClientManager? {
|
||||
return Mockito.mock<ReactiveOAuth2AuthorizedClientManager?>(ReactiveOAuth2AuthorizedClientManager::class.java)
|
||||
}
|
||||
|
||||
@Bean
|
||||
fun groupConfigurer(server: MockWebServer): WebClientHttpServiceGroupConfigurer {
|
||||
return WebClientHttpServiceGroupConfigurer { groups: HttpServiceGroupConfigurer.Groups<WebClient.Builder?>? ->
|
||||
val baseUrl = server.url("").toString()
|
||||
groups!!
|
||||
.forEachClient(ClientCallback { group: HttpServiceGroup?, builder: WebClient.Builder? ->
|
||||
builder!!
|
||||
.baseUrl(baseUrl)
|
||||
.defaultHeader("Accept", "application/vnd.github.v3+json")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@Bean
|
||||
fun mockServer(): MockWebServer {
|
||||
return MockWebServer()
|
||||
}
|
||||
}
|
@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright 2002-2025 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.oauth2.client.annotation;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import org.springframework.core.annotation.AliasFor;
|
||||
|
||||
/**
|
||||
* This annotation can be added to the method of an interface based HTTP client created
|
||||
* using {@link org.springframework.web.service.invoker.HttpServiceProxyFactory} to
|
||||
* automatically associate an OAuth token with the request.
|
||||
*
|
||||
* @author Rob Winch
|
||||
* @since 7.0
|
||||
* @see org.springframework.security.oauth2.client.web.client.ClientRegistrationIdProcessor
|
||||
*/
|
||||
@Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE })
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
public @interface ClientRegistrationId {
|
||||
|
||||
/**
|
||||
* Sets the client registration identifier.
|
||||
* @return the client registration identifier
|
||||
*/
|
||||
@AliasFor("value")
|
||||
String registrationId() default "";
|
||||
|
||||
/**
|
||||
* The default attribute for this annotation. This is an alias for
|
||||
* {@link #registrationId()}. For example,
|
||||
* {@code @RegisteredOAuth2AuthorizedClient("login-client")} is equivalent to
|
||||
* {@code @RegisteredOAuth2AuthorizedClient(registrationId="login-client")}.
|
||||
* @return the client registration identifier
|
||||
*/
|
||||
@AliasFor("registrationId")
|
||||
String value() default "";
|
||||
|
||||
}
|
@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Copyright 2002-2025 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.oauth2.client.web;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Used for accessing the attribute that stores the the
|
||||
* {@link ClientRegistration#getRegistrationId()}. This ensures that
|
||||
* {@link org.springframework.security.oauth2.client.web.client.ClientRegistrationIdProcessor}
|
||||
* aligns with all of ways of setting on both
|
||||
* {@link org.springframework.web.client.RestClient} and
|
||||
* {@link org.springframework.web.reactive.function.client.WebClient}.
|
||||
*
|
||||
* @see org.springframework.security.oauth2.client.web.client.ClientRegistrationIdProcessor
|
||||
* @see org.springframework.security.oauth2.client.web.client.RequestAttributeClientRegistrationIdResolver
|
||||
* @see org.springframework.security.oauth2.client.web.reactive.function.client.ServerOAuth2AuthorizedClientExchangeFilterFunction
|
||||
* @see org.springframework.security.oauth2.client.web.reactive.function.client.ServletOAuth2AuthorizedClientExchangeFilterFunction
|
||||
*/
|
||||
public final class ClientAttributes {
|
||||
|
||||
private static final String CLIENT_REGISTRATION_ID_ATTR_NAME = ClientRegistration.class.getName()
|
||||
.concat(".CLIENT_REGISTRATION_ID");
|
||||
|
||||
/**
|
||||
* Resolves the {@link ClientRegistration#getRegistrationId() clientRegistrationId} to
|
||||
* be used to look up the {@link OAuth2AuthorizedClient}.
|
||||
* @param attributes the to search
|
||||
* @return the registration id to use.
|
||||
*/
|
||||
public static String resolveClientRegistrationId(Map<String, Object> attributes) {
|
||||
return (String) attributes.get(CLIENT_REGISTRATION_ID_ATTR_NAME);
|
||||
}
|
||||
|
||||
/**
|
||||
* Produces a Consumer that adds the {@link ClientRegistration#getRegistrationId()
|
||||
* clientRegistrationId} to be used to look up the {@link OAuth2AuthorizedClient}.
|
||||
* @param clientRegistrationId the {@link ClientRegistration#getRegistrationId()
|
||||
* clientRegistrationId} to be used to look up the {@link OAuth2AuthorizedClient}
|
||||
* @return the {@link Consumer} to populate the attributes
|
||||
*/
|
||||
public static Consumer<Map<String, Object>> clientRegistrationId(String clientRegistrationId) {
|
||||
Assert.hasText(clientRegistrationId, "clientRegistrationId cannot be empty");
|
||||
return (attributes) -> attributes.put(CLIENT_REGISTRATION_ID_ATTR_NAME, clientRegistrationId);
|
||||
}
|
||||
|
||||
private ClientAttributes() {
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Copyright 2002-2025 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.oauth2.client.web.client;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.security.oauth2.client.annotation.ClientRegistrationId;
|
||||
import org.springframework.security.oauth2.client.web.ClientAttributes;
|
||||
import org.springframework.web.service.invoker.HttpRequestValues;
|
||||
|
||||
/**
|
||||
* Invokes {@link ClientAttributes#clientRegistrationId(String)} with the value specified
|
||||
* by {@link ClientRegistrationId} on the request.
|
||||
*
|
||||
* @author Rob Winch
|
||||
* @since 7.0
|
||||
*/
|
||||
public final class ClientRegistrationIdProcessor implements HttpRequestValues.Processor {
|
||||
|
||||
public static ClientRegistrationIdProcessor DEFAULT_INSTANCE = new ClientRegistrationIdProcessor();
|
||||
|
||||
@Override
|
||||
public void process(Method method, @Nullable Object[] arguments, HttpRequestValues.Builder builder) {
|
||||
ClientRegistrationId registeredId = AnnotationUtils.findAnnotation(method, ClientRegistrationId.class);
|
||||
if (registeredId != null) {
|
||||
String registrationId = registeredId.registrationId();
|
||||
builder.configureAttributes(ClientAttributes.clientRegistrationId(registrationId));
|
||||
}
|
||||
}
|
||||
|
||||
private ClientRegistrationIdProcessor() {
|
||||
}
|
||||
|
||||
}
|
@ -23,7 +23,7 @@ import org.springframework.http.HttpRequest;
|
||||
import org.springframework.http.client.ClientHttpRequest;
|
||||
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.security.oauth2.client.web.ClientAttributes;
|
||||
|
||||
/**
|
||||
* A strategy for resolving a {@code clientRegistrationId} from an intercepted request
|
||||
@ -36,13 +36,9 @@ import org.springframework.util.Assert;
|
||||
public final class RequestAttributeClientRegistrationIdResolver
|
||||
implements OAuth2ClientHttpRequestInterceptor.ClientRegistrationIdResolver {
|
||||
|
||||
private static final String CLIENT_REGISTRATION_ID_ATTR_NAME = RequestAttributeClientRegistrationIdResolver.class
|
||||
.getName()
|
||||
.concat(".clientRegistrationId");
|
||||
|
||||
@Override
|
||||
public String resolve(HttpRequest request) {
|
||||
return (String) request.getAttributes().get(CLIENT_REGISTRATION_ID_ATTR_NAME);
|
||||
return ClientAttributes.resolveClientRegistrationId(request.getAttributes());
|
||||
}
|
||||
|
||||
/**
|
||||
@ -54,8 +50,7 @@ public final class RequestAttributeClientRegistrationIdResolver
|
||||
* @return the {@link Consumer} to populate the attributes
|
||||
*/
|
||||
public static Consumer<Map<String, Object>> clientRegistrationId(String clientRegistrationId) {
|
||||
Assert.hasText(clientRegistrationId, "clientRegistrationId cannot be empty");
|
||||
return (attributes) -> attributes.put(CLIENT_REGISTRATION_ID_ATTR_NAME, clientRegistrationId);
|
||||
return ClientAttributes.clientRegistrationId(clientRegistrationId);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,67 @@
|
||||
/*
|
||||
* Copyright 2002-2025 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.oauth2.client.web.client.support;
|
||||
|
||||
import org.springframework.http.client.ClientHttpRequestInterceptor;
|
||||
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientManager;
|
||||
import org.springframework.security.oauth2.client.web.client.ClientRegistrationIdProcessor;
|
||||
import org.springframework.security.oauth2.client.web.client.OAuth2ClientHttpRequestInterceptor;
|
||||
import org.springframework.web.client.RestClient;
|
||||
import org.springframework.web.client.support.RestClientHttpServiceGroupConfigurer;
|
||||
import org.springframework.web.service.invoker.HttpRequestValues;
|
||||
|
||||
/**
|
||||
* Simplify adding OAuth2 support to interface based rest clients that use
|
||||
* {@link RestClient}.
|
||||
*
|
||||
* It will add {@link OAuth2ClientHttpRequestInterceptor} to the {@link RestClient} and
|
||||
* {@link ClientRegistrationIdProcessor} to the
|
||||
* {@link org.springframework.web.service.invoker.HttpServiceProxyFactory}.
|
||||
*
|
||||
* @author Rob Winch
|
||||
* @since 7.0
|
||||
*/
|
||||
public final class OAuth2RestClientHttpServiceGroupConfigurer implements RestClientHttpServiceGroupConfigurer {
|
||||
|
||||
private final HttpRequestValues.Processor processor = ClientRegistrationIdProcessor.DEFAULT_INSTANCE;
|
||||
|
||||
private final ClientHttpRequestInterceptor interceptor;
|
||||
|
||||
private OAuth2RestClientHttpServiceGroupConfigurer(ClientHttpRequestInterceptor interceptor) {
|
||||
this.interceptor = interceptor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configureGroups(Groups<RestClient.Builder> groups) {
|
||||
// @formatter:off
|
||||
groups.forEachClient((group, client) ->
|
||||
client.requestInterceptor(this.interceptor)
|
||||
);
|
||||
groups.forEachProxyFactory((group, factory) ->
|
||||
factory.httpRequestValuesProcessor(this.processor)
|
||||
);
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
public static OAuth2RestClientHttpServiceGroupConfigurer from(
|
||||
OAuth2AuthorizedClientManager authorizedClientManager) {
|
||||
OAuth2ClientHttpRequestInterceptor interceptor = new OAuth2ClientHttpRequestInterceptor(
|
||||
authorizedClientManager);
|
||||
return new OAuth2RestClientHttpServiceGroupConfigurer(interceptor);
|
||||
}
|
||||
|
||||
}
|
@ -44,6 +44,7 @@ import org.springframework.security.oauth2.client.RemoveAuthorizedClientReactive
|
||||
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||
import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository;
|
||||
import org.springframework.security.oauth2.client.web.ClientAttributes;
|
||||
import org.springframework.security.oauth2.client.web.DefaultReactiveOAuth2AuthorizedClientManager;
|
||||
import org.springframework.security.oauth2.client.web.server.ServerOAuth2AuthorizedClientRepository;
|
||||
import org.springframework.security.oauth2.core.OAuth2AuthorizationException;
|
||||
@ -104,13 +105,6 @@ public final class ServerOAuth2AuthorizedClientExchangeFilterFunction implements
|
||||
*/
|
||||
private static final String OAUTH2_AUTHORIZED_CLIENT_ATTR_NAME = OAuth2AuthorizedClient.class.getName();
|
||||
|
||||
/**
|
||||
* The client request attribute name used to locate the
|
||||
* {@link ClientRegistration#getRegistrationId()}
|
||||
*/
|
||||
private static final String CLIENT_REGISTRATION_ID_ATTR_NAME = OAuth2AuthorizedClient.class.getName()
|
||||
.concat(".CLIENT_REGISTRATION_ID");
|
||||
|
||||
/**
|
||||
* The request attribute name used to locate the
|
||||
* {@link org.springframework.web.server.ServerWebExchange}.
|
||||
@ -292,7 +286,7 @@ public final class ServerOAuth2AuthorizedClientExchangeFilterFunction implements
|
||||
* @return the {@link Consumer} to populate the attributes
|
||||
*/
|
||||
public static Consumer<Map<String, Object>> clientRegistrationId(String clientRegistrationId) {
|
||||
return (attributes) -> attributes.put(CLIENT_REGISTRATION_ID_ATTR_NAME, clientRegistrationId);
|
||||
return ClientAttributes.clientRegistrationId(clientRegistrationId);
|
||||
}
|
||||
|
||||
private static String clientRegistrationId(ClientRequest request) {
|
||||
@ -300,7 +294,7 @@ public final class ServerOAuth2AuthorizedClientExchangeFilterFunction implements
|
||||
if (authorizedClient != null) {
|
||||
return authorizedClient.getClientRegistration().getRegistrationId();
|
||||
}
|
||||
return (String) request.attributes().get(CLIENT_REGISTRATION_ID_ATTR_NAME);
|
||||
return ClientAttributes.resolveClientRegistrationId(request.attributes());
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -50,6 +50,7 @@ import org.springframework.security.oauth2.client.RemoveAuthorizedClientOAuth2Au
|
||||
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
|
||||
import org.springframework.security.oauth2.client.web.ClientAttributes;
|
||||
import org.springframework.security.oauth2.client.web.DefaultOAuth2AuthorizedClientManager;
|
||||
import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository;
|
||||
import org.springframework.security.oauth2.core.OAuth2AuthorizationException;
|
||||
@ -136,9 +137,6 @@ public final class ServletOAuth2AuthorizedClientExchangeFilterFunction implement
|
||||
*/
|
||||
private static final String OAUTH2_AUTHORIZED_CLIENT_ATTR_NAME = OAuth2AuthorizedClient.class.getName();
|
||||
|
||||
private static final String CLIENT_REGISTRATION_ID_ATTR_NAME = OAuth2AuthorizedClient.class.getName()
|
||||
.concat(".CLIENT_REGISTRATION_ID");
|
||||
|
||||
private static final String AUTHENTICATION_ATTR_NAME = Authentication.class.getName();
|
||||
|
||||
private static final String HTTP_SERVLET_REQUEST_ATTR_NAME = HttpServletRequest.class.getName();
|
||||
@ -311,7 +309,7 @@ public final class ServletOAuth2AuthorizedClientExchangeFilterFunction implement
|
||||
* @return the {@link Consumer} to populate the attributes
|
||||
*/
|
||||
public static Consumer<Map<String, Object>> clientRegistrationId(String clientRegistrationId) {
|
||||
return (attributes) -> attributes.put(CLIENT_REGISTRATION_ID_ATTR_NAME, clientRegistrationId);
|
||||
return ClientAttributes.clientRegistrationId(clientRegistrationId);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -536,7 +534,7 @@ public final class ServletOAuth2AuthorizedClientExchangeFilterFunction implement
|
||||
}
|
||||
|
||||
static String getClientRegistrationId(Map<String, Object> attrs) {
|
||||
return (String) attrs.get(CLIENT_REGISTRATION_ID_ATTR_NAME);
|
||||
return ClientAttributes.resolveClientRegistrationId(attrs);
|
||||
}
|
||||
|
||||
static Authentication getAuthentication(Map<String, Object> attrs) {
|
||||
|
@ -0,0 +1,92 @@
|
||||
/*
|
||||
* Copyright 2002-2025 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.oauth2.client.web.reactive.function.client.support;
|
||||
|
||||
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientManager;
|
||||
import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientManager;
|
||||
import org.springframework.security.oauth2.client.web.client.ClientRegistrationIdProcessor;
|
||||
import org.springframework.security.oauth2.client.web.reactive.function.client.ServerOAuth2AuthorizedClientExchangeFilterFunction;
|
||||
import org.springframework.security.oauth2.client.web.reactive.function.client.ServletOAuth2AuthorizedClientExchangeFilterFunction;
|
||||
import org.springframework.web.reactive.function.client.ExchangeFilterFunction;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
import org.springframework.web.reactive.function.client.support.WebClientHttpServiceGroupConfigurer;
|
||||
import org.springframework.web.service.invoker.HttpRequestValues;
|
||||
|
||||
/**
|
||||
* Simplify adding OAuth2 support to interface based rest clients that use
|
||||
* {@link WebClient}.
|
||||
*
|
||||
* @author Rob Winch
|
||||
* @since 7.0
|
||||
*/
|
||||
public final class OAuth2WebClientHttpServiceGroupConfigurer implements WebClientHttpServiceGroupConfigurer {
|
||||
|
||||
private final HttpRequestValues.Processor processor = ClientRegistrationIdProcessor.DEFAULT_INSTANCE;
|
||||
|
||||
private final ExchangeFilterFunction filter;
|
||||
|
||||
private OAuth2WebClientHttpServiceGroupConfigurer(ExchangeFilterFunction filter) {
|
||||
this.filter = filter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configureGroups(Groups<WebClient.Builder> groups) {
|
||||
// @formatter:off
|
||||
groups.forEachClient((group, client) ->
|
||||
client.filter(this.filter)
|
||||
);
|
||||
groups.forEachProxyFactory((group, factory) ->
|
||||
factory.httpRequestValuesProcessor(this.processor)
|
||||
);
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an instance for Reactive web applications from the provided
|
||||
* {@link ReactiveOAuth2AuthorizedClientManager}.
|
||||
*
|
||||
* It will add {@link ServerOAuth2AuthorizedClientExchangeFilterFunction} to the
|
||||
* {@link WebClient} and {@link ClientRegistrationIdProcessor} to the
|
||||
* {@link org.springframework.web.service.invoker.HttpServiceProxyFactory}.
|
||||
* @param authorizedClientManager the manager to use.
|
||||
* @return the {@link OAuth2WebClientHttpServiceGroupConfigurer}.
|
||||
*/
|
||||
public static OAuth2WebClientHttpServiceGroupConfigurer from(
|
||||
ReactiveOAuth2AuthorizedClientManager authorizedClientManager) {
|
||||
ServerOAuth2AuthorizedClientExchangeFilterFunction filter = new ServerOAuth2AuthorizedClientExchangeFilterFunction(
|
||||
authorizedClientManager);
|
||||
return new OAuth2WebClientHttpServiceGroupConfigurer(filter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an instance for Servlet based environments from the provided
|
||||
* {@link OAuth2AuthorizedClientManager}.
|
||||
*
|
||||
* It will add {@link ServletOAuth2AuthorizedClientExchangeFilterFunction} to the
|
||||
* {@link WebClient} and {@link ClientRegistrationIdProcessor} to the
|
||||
* {@link org.springframework.web.service.invoker.HttpServiceProxyFactory}.
|
||||
* @param authorizedClientManager the manager to use.
|
||||
* @return the {@link OAuth2WebClientHttpServiceGroupConfigurer}.
|
||||
*/
|
||||
public static OAuth2WebClientHttpServiceGroupConfigurer from(
|
||||
OAuth2AuthorizedClientManager authorizedClientManager) {
|
||||
ServletOAuth2AuthorizedClientExchangeFilterFunction filter = new ServletOAuth2AuthorizedClientExchangeFilterFunction(
|
||||
authorizedClientManager);
|
||||
return new OAuth2WebClientHttpServiceGroupConfigurer(filter);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,108 @@
|
||||
/*
|
||||
* Copyright 2002-2025 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.oauth2.client.web.client;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import okhttp3.HttpUrl;
|
||||
import okhttp3.mockwebserver.MockResponse;
|
||||
import okhttp3.mockwebserver.MockWebServer;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
|
||||
import org.springframework.security.oauth2.client.annotation.ClientRegistrationId;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||
import org.springframework.security.oauth2.client.registration.TestClientRegistrations;
|
||||
import org.springframework.security.oauth2.core.OAuth2AccessToken;
|
||||
import org.springframework.security.oauth2.core.TestOAuth2AccessTokens;
|
||||
import org.springframework.web.service.annotation.GetExchange;
|
||||
import org.springframework.web.service.invoker.HttpExchangeAdapter;
|
||||
import org.springframework.web.service.invoker.HttpServiceProxyFactory;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Base class for integration testing {@link ClientRegistrationIdProcessor} with
|
||||
* {@link MockWebServer}.
|
||||
*
|
||||
* @author Rob Winch
|
||||
* @since 7.0
|
||||
*/
|
||||
abstract class AbstractMockServerClientRegistrationIdProcessorTests {
|
||||
|
||||
static final String REGISTRATION_ID = "okta";
|
||||
|
||||
private final MockWebServer server = new MockWebServer();
|
||||
|
||||
private OAuth2AccessToken accessToken;
|
||||
|
||||
protected String baseUrl;
|
||||
|
||||
protected OAuth2AuthorizedClient authorizedClient;
|
||||
|
||||
@BeforeEach
|
||||
void setup() throws IOException {
|
||||
this.server.start();
|
||||
HttpUrl url = this.server.url("/range/");
|
||||
this.baseUrl = url.toString();
|
||||
ClientRegistration clientRegistration = TestClientRegistrations.clientRegistration()
|
||||
.registrationId(REGISTRATION_ID)
|
||||
.build();
|
||||
this.accessToken = TestOAuth2AccessTokens.scopes("read", "write");
|
||||
this.authorizedClient = new OAuth2AuthorizedClient(clientRegistration, "user", this.accessToken);
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void cleanup() throws IOException {
|
||||
if (this.server != null) {
|
||||
this.server.shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
void testWithAdapter(HttpExchangeAdapter adapter) throws InterruptedException {
|
||||
ClientRegistrationIdProcessor processor = ClientRegistrationIdProcessor.DEFAULT_INSTANCE;
|
||||
HttpServiceProxyFactory factory = HttpServiceProxyFactory.builder()
|
||||
.exchangeAdapter(adapter)
|
||||
.httpRequestValuesProcessor(processor)
|
||||
.build();
|
||||
MessageClient messages = factory.createClient(MessageClient.class);
|
||||
|
||||
this.server.enqueue(new MockResponse().setBody("Hello OAuth2!").setResponseCode(200));
|
||||
assertThat(messages.getMessage()).isEqualTo("Hello OAuth2!");
|
||||
|
||||
String authorizationHeader = this.server.takeRequest().getHeader(HttpHeaders.AUTHORIZATION);
|
||||
assertOAuthTokenValue(authorizationHeader, this.accessToken);
|
||||
|
||||
}
|
||||
|
||||
private static void assertOAuthTokenValue(String value, OAuth2AccessToken accessToken) {
|
||||
String tokenType = accessToken.getTokenType().getValue();
|
||||
String tokenValue = accessToken.getTokenValue();
|
||||
assertThat(value).isEqualTo("%s %s".formatted(tokenType, tokenValue));
|
||||
}
|
||||
|
||||
interface MessageClient {
|
||||
|
||||
@GetExchange("/message")
|
||||
@ClientRegistrationId(REGISTRATION_ID)
|
||||
String getMessage();
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright 2002-2025 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.oauth2.client.web.client;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import org.springframework.security.oauth2.client.OAuth2AuthorizeRequest;
|
||||
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientManager;
|
||||
import org.springframework.web.client.RestClient;
|
||||
import org.springframework.web.client.support.RestClientAdapter;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
|
||||
/**
|
||||
* Runs tests of {@link ClientRegistrationIdProcessor} with {@link RestClient} to ensure
|
||||
* that all the parts work together properly.
|
||||
*
|
||||
* @author Rob Winch
|
||||
* @since 7.0
|
||||
*/
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class ClientRegistrationIdProcessorRestClientTests extends AbstractMockServerClientRegistrationIdProcessorTests {
|
||||
|
||||
@Mock
|
||||
private OAuth2AuthorizedClientManager authorizedClientManager;
|
||||
|
||||
@Test
|
||||
void clientRegistrationIdProcessorWorksWithRestClientAdapter() throws InterruptedException {
|
||||
OAuth2ClientHttpRequestInterceptor interceptor = new OAuth2ClientHttpRequestInterceptor(
|
||||
this.authorizedClientManager);
|
||||
RestClient.Builder builder = RestClient.builder().requestInterceptor(interceptor).baseUrl(this.baseUrl);
|
||||
|
||||
ArgumentCaptor<OAuth2AuthorizeRequest> authorizeRequest = ArgumentCaptor.forClass(OAuth2AuthorizeRequest.class);
|
||||
given(this.authorizedClientManager.authorize(authorizeRequest.capture())).willReturn(authorizedClient);
|
||||
|
||||
testWithAdapter(RestClientAdapter.create(builder.build()));
|
||||
|
||||
assertThat(authorizeRequest.getValue().getClientRegistrationId()).isEqualTo(REGISTRATION_ID);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,94 @@
|
||||
/*
|
||||
* Copyright 2002-2025 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.oauth2.client.web.client;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.security.oauth2.client.annotation.ClientRegistrationId;
|
||||
import org.springframework.security.oauth2.client.web.ClientAttributes;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
import org.springframework.web.service.invoker.HttpRequestValues;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link ClientRegistrationIdProcessor}.
|
||||
*
|
||||
* @author Rob Winch
|
||||
* @since 7.0
|
||||
* @see ClientRegistrationIdProcessorWebClientTests
|
||||
* @see ClientRegistrationIdProcessorRestClientTests
|
||||
*/
|
||||
class ClientRegistrationIdProcessorTests {
|
||||
|
||||
ClientRegistrationIdProcessor processor = ClientRegistrationIdProcessor.DEFAULT_INSTANCE;
|
||||
|
||||
@Test
|
||||
void processWhenClientRegistrationIdPresentThenSet() {
|
||||
HttpRequestValues.Builder builder = HttpRequestValues.builder();
|
||||
Method hasClientRegistrationId = ReflectionUtils.findMethod(RestService.class, "hasClientRegistrationId");
|
||||
this.processor.process(hasClientRegistrationId, null, builder);
|
||||
|
||||
String registrationId = ClientAttributes.resolveClientRegistrationId(builder.build().getAttributes());
|
||||
assertThat(registrationId).isEqualTo(RestService.REGISTRATION_ID);
|
||||
}
|
||||
|
||||
@Test
|
||||
void processWhenMetaClientRegistrationIdPresentThenSet() {
|
||||
HttpRequestValues.Builder builder = HttpRequestValues.builder();
|
||||
Method hasClientRegistrationId = ReflectionUtils.findMethod(RestService.class, "hasMetaClientRegistrationId");
|
||||
this.processor.process(hasClientRegistrationId, null, builder);
|
||||
|
||||
String registrationId = ClientAttributes.resolveClientRegistrationId(builder.build().getAttributes());
|
||||
assertThat(registrationId).isEqualTo(RestService.REGISTRATION_ID);
|
||||
}
|
||||
|
||||
@Test
|
||||
void processWhenNoClientRegistrationIdPresentThenNull() {
|
||||
HttpRequestValues.Builder builder = HttpRequestValues.builder();
|
||||
Method hasClientRegistrationId = ReflectionUtils.findMethod(RestService.class, "noClientRegistrationId");
|
||||
this.processor.process(hasClientRegistrationId, null, builder);
|
||||
|
||||
String registrationId = ClientAttributes.resolveClientRegistrationId(builder.build().getAttributes());
|
||||
assertThat(registrationId).isNull();
|
||||
}
|
||||
|
||||
interface RestService {
|
||||
|
||||
String REGISTRATION_ID = "registrationId";
|
||||
|
||||
@ClientRegistrationId(REGISTRATION_ID)
|
||||
void hasClientRegistrationId();
|
||||
|
||||
@MetaClientRegistrationId
|
||||
void hasMetaClientRegistrationId();
|
||||
|
||||
void noClientRegistrationId();
|
||||
|
||||
}
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@ClientRegistrationId(RestService.REGISTRATION_ID)
|
||||
@interface MetaClientRegistrationId {
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,82 @@
|
||||
/*
|
||||
* Copyright 2002-2025 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.oauth2.client.web.client;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.security.oauth2.client.OAuth2AuthorizeRequest;
|
||||
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientManager;
|
||||
import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientManager;
|
||||
import org.springframework.security.oauth2.client.web.reactive.function.client.ServerOAuth2AuthorizedClientExchangeFilterFunction;
|
||||
import org.springframework.security.oauth2.client.web.reactive.function.client.ServletOAuth2AuthorizedClientExchangeFilterFunction;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
import org.springframework.web.reactive.function.client.support.WebClientAdapter;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* Runs tests for {@link ClientRegistrationIdProcessor} with {@link WebClient} to ensure
|
||||
* that all the parts work together properly.
|
||||
*
|
||||
* @author Rob Winch
|
||||
* @since 7.0
|
||||
*/
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class ClientRegistrationIdProcessorWebClientTests extends AbstractMockServerClientRegistrationIdProcessorTests {
|
||||
|
||||
@Test
|
||||
void clientRegistrationIdProcessorWorksWithReactiveWebClient() throws InterruptedException {
|
||||
ReactiveOAuth2AuthorizedClientManager authorizedClientManager = mock(
|
||||
ReactiveOAuth2AuthorizedClientManager.class);
|
||||
ServerOAuth2AuthorizedClientExchangeFilterFunction oauth2Client = new ServerOAuth2AuthorizedClientExchangeFilterFunction(
|
||||
authorizedClientManager);
|
||||
|
||||
WebClient.Builder builder = WebClient.builder().filter(oauth2Client).baseUrl(this.baseUrl);
|
||||
|
||||
ArgumentCaptor<OAuth2AuthorizeRequest> authorizeRequest = ArgumentCaptor.forClass(OAuth2AuthorizeRequest.class);
|
||||
given(authorizedClientManager.authorize(authorizeRequest.capture()))
|
||||
.willReturn(Mono.just(this.authorizedClient));
|
||||
|
||||
testWithAdapter(WebClientAdapter.create(builder.build()));
|
||||
|
||||
assertThat(authorizeRequest.getValue().getClientRegistrationId()).isEqualTo(REGISTRATION_ID);
|
||||
}
|
||||
|
||||
@Test
|
||||
void clientRegistrationIdProcessorWorksWithServletWebClient() throws InterruptedException {
|
||||
OAuth2AuthorizedClientManager authorizedClientManager = mock(OAuth2AuthorizedClientManager.class);
|
||||
|
||||
ServletOAuth2AuthorizedClientExchangeFilterFunction oauth2Client = new ServletOAuth2AuthorizedClientExchangeFilterFunction(
|
||||
authorizedClientManager);
|
||||
|
||||
WebClient.Builder builder = WebClient.builder().filter(oauth2Client).baseUrl(this.baseUrl);
|
||||
|
||||
ArgumentCaptor<OAuth2AuthorizeRequest> authorizeRequest = ArgumentCaptor.forClass(OAuth2AuthorizeRequest.class);
|
||||
given(authorizedClientManager.authorize(authorizeRequest.capture())).willReturn(this.authorizedClient);
|
||||
|
||||
testWithAdapter(WebClientAdapter.create(builder.build()));
|
||||
|
||||
assertThat(authorizeRequest.getValue().getClientRegistrationId()).isEqualTo(REGISTRATION_ID);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,89 @@
|
||||
/*
|
||||
* Copyright 2002-2025 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.oauth2.client.web.client.support;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Captor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientManager;
|
||||
import org.springframework.security.oauth2.client.web.client.ClientRegistrationIdProcessor;
|
||||
import org.springframework.security.oauth2.client.web.client.OAuth2ClientHttpRequestInterceptor;
|
||||
import org.springframework.web.client.RestClient;
|
||||
import org.springframework.web.service.invoker.HttpServiceProxyFactory;
|
||||
import org.springframework.web.service.registry.HttpServiceGroupConfigurer;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
/**
|
||||
* Tests {@link OAuth2RestClientHttpServiceGroupConfigurer}.
|
||||
*
|
||||
* @author Rob Winch
|
||||
*/
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class OAuth2RestClientHttpServiceGroupConfigurerTests {
|
||||
|
||||
@Mock
|
||||
private OAuth2AuthorizedClientManager authoriedClientManager;
|
||||
|
||||
@Mock
|
||||
private HttpServiceGroupConfigurer.Groups<RestClient.Builder> groups;
|
||||
|
||||
@Captor
|
||||
ArgumentCaptor<HttpServiceGroupConfigurer.ProxyFactoryCallback> forProxyFactory;
|
||||
|
||||
@Mock
|
||||
private HttpServiceProxyFactory.Builder factoryBuilder;
|
||||
|
||||
@Captor
|
||||
private ArgumentCaptor<HttpServiceGroupConfigurer.ClientCallback<RestClient.Builder>> configureClient;
|
||||
|
||||
@Mock
|
||||
private RestClient.Builder clientBuilder;
|
||||
|
||||
@Test
|
||||
void configureGroupsConfigureProxyFactory() {
|
||||
|
||||
OAuth2RestClientHttpServiceGroupConfigurer configurer = OAuth2RestClientHttpServiceGroupConfigurer
|
||||
.from(this.authoriedClientManager);
|
||||
|
||||
configurer.configureGroups(this.groups);
|
||||
verify(this.groups).forEachProxyFactory(this.forProxyFactory.capture());
|
||||
|
||||
this.forProxyFactory.getValue().withProxyFactory(null, this.factoryBuilder);
|
||||
|
||||
verify(this.factoryBuilder).httpRequestValuesProcessor(ClientRegistrationIdProcessor.DEFAULT_INSTANCE);
|
||||
}
|
||||
|
||||
@Test
|
||||
void configureGroupsConfigureClient() {
|
||||
OAuth2RestClientHttpServiceGroupConfigurer configurer = OAuth2RestClientHttpServiceGroupConfigurer
|
||||
.from(this.authoriedClientManager);
|
||||
|
||||
configurer.configureGroups(this.groups);
|
||||
verify(this.groups).forEachClient(this.configureClient.capture());
|
||||
|
||||
this.configureClient.getValue().withClient(null, this.clientBuilder);
|
||||
|
||||
verify(this.clientBuilder).requestInterceptor(any(OAuth2ClientHttpRequestInterceptor.class));
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,89 @@
|
||||
/*
|
||||
* Copyright 2002-2025 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.oauth2.client.web.reactive.function.client.support;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Captor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientManager;
|
||||
import org.springframework.security.oauth2.client.web.client.ClientRegistrationIdProcessor;
|
||||
import org.springframework.web.reactive.function.client.ExchangeFilterFunction;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
import org.springframework.web.service.invoker.HttpServiceProxyFactory;
|
||||
import org.springframework.web.service.registry.HttpServiceGroupConfigurer;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
/**
|
||||
* Tests {@link OAuth2WebClientHttpServiceGroupConfigurer}.
|
||||
*
|
||||
* @author Rob Winch
|
||||
*/
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class OAuth2WebClientHttpServiceGroupConfigurerTests {
|
||||
|
||||
@Mock
|
||||
private OAuth2AuthorizedClientManager authoriedClientManager;
|
||||
|
||||
@Mock
|
||||
private HttpServiceGroupConfigurer.Groups<WebClient.Builder> groups;
|
||||
|
||||
@Captor
|
||||
ArgumentCaptor<HttpServiceGroupConfigurer.ProxyFactoryCallback> forProxyFactory;
|
||||
|
||||
@Mock
|
||||
private HttpServiceProxyFactory.Builder factoryBuilder;
|
||||
|
||||
@Captor
|
||||
private ArgumentCaptor<HttpServiceGroupConfigurer.ClientCallback<WebClient.Builder>> configureClient;
|
||||
|
||||
@Mock
|
||||
private WebClient.Builder clientBuilder;
|
||||
|
||||
@Test
|
||||
void configureGroupsConfigureProxyFactory() {
|
||||
|
||||
OAuth2WebClientHttpServiceGroupConfigurer configurer = OAuth2WebClientHttpServiceGroupConfigurer
|
||||
.from(this.authoriedClientManager);
|
||||
|
||||
configurer.configureGroups(this.groups);
|
||||
verify(this.groups).forEachProxyFactory(this.forProxyFactory.capture());
|
||||
|
||||
this.forProxyFactory.getValue().withProxyFactory(null, this.factoryBuilder);
|
||||
|
||||
verify(this.factoryBuilder).httpRequestValuesProcessor(ClientRegistrationIdProcessor.DEFAULT_INSTANCE);
|
||||
}
|
||||
|
||||
@Test
|
||||
void configureGroupsConfigureClient() {
|
||||
OAuth2WebClientHttpServiceGroupConfigurer configurer = OAuth2WebClientHttpServiceGroupConfigurer
|
||||
.from(this.authoriedClientManager);
|
||||
|
||||
configurer.configureGroups(this.groups);
|
||||
verify(this.groups).forEachClient(this.configureClient.capture());
|
||||
|
||||
this.configureClient.getValue().withClient(null, this.clientBuilder);
|
||||
|
||||
verify(this.clientBuilder).filter(any(ExchangeFilterFunction.class));
|
||||
}
|
||||
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user