31 changed files with 1647 additions and 22 deletions
@ -0,0 +1,66 @@
@@ -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`. |
||||
|
||||
@ -0,0 +1,28 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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() { |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,67 @@
@@ -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); |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,92 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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…
Reference in new issue