Skip to content

Commit f294f05

Browse files
committed
test: Add integration tests for config client oauth2 support
Signed-off-by: prafsoni <prafsoni@gmail.com>
1 parent 836e6fd commit f294f05

4 files changed

Lines changed: 274 additions & 0 deletions

File tree

pom.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@
107107
<module>spring-cloud-config-sample</module>
108108
<module>spring-cloud-starter-config</module>
109109
<module>spring-cloud-config-client-tls-tests</module>
110+
<module>spring-cloud-config-client-oauth2-tests</module>
110111
<module>docs</module>
111112
</modules>
112113
<dependencyManagement>
@@ -178,6 +179,7 @@
178179
<configuration>
179180
<excludeArtifacts>
180181
<artifact>spring-cloud-config-client-tls-tests</artifact>
182+
<artifact>spring-cloud-config-client-oauth2-tests</artifact>
181183
<artifact>spring-cloud-config-sample</artifact>
182184
</excludeArtifacts>
183185
</configuration>
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3+
xmlns="http://maven.apache.org/POM/4.0.0"
4+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
5+
<modelVersion>4.0.0</modelVersion>
6+
<artifactId>spring-cloud-config-client-oauth2-tests</artifactId>
7+
<packaging>jar</packaging>
8+
<name>Spring Cloud Config Client OAuth2 Tests</name>
9+
10+
<parent>
11+
<groupId>org.springframework.cloud</groupId>
12+
<artifactId>spring-cloud-config</artifactId>
13+
<version>5.0.0-SNAPSHOT</version>
14+
<relativePath>..</relativePath>
15+
</parent>
16+
17+
<url>https://spring.io</url>
18+
<description>Spring Cloud Config Client OAuth2 Integration Tests</description>
19+
20+
<dependencies>
21+
<dependency>
22+
<groupId>org.springframework.cloud</groupId>
23+
<artifactId>spring-cloud-config-client</artifactId>
24+
<version>${project.version}</version>
25+
</dependency>
26+
<dependency>
27+
<groupId>org.springframework.boot</groupId>
28+
<artifactId>spring-boot</artifactId>
29+
</dependency>
30+
<dependency>
31+
<groupId>org.springframework.boot</groupId>
32+
<artifactId>spring-boot-autoconfigure</artifactId>
33+
</dependency>
34+
<dependency>
35+
<groupId>org.springframework</groupId>
36+
<artifactId>spring-web</artifactId>
37+
</dependency>
38+
39+
<!-- Testing -->
40+
<dependency>
41+
<groupId>org.springframework.boot</groupId>
42+
<artifactId>spring-boot-starter-test</artifactId>
43+
<scope>test</scope>
44+
</dependency>
45+
<dependency>
46+
<groupId>org.junit.platform</groupId>
47+
<artifactId>junit-platform-launcher</artifactId>
48+
<scope>test</scope>
49+
</dependency>
50+
<dependency>
51+
<groupId>org.springframework.cloud</groupId>
52+
<artifactId>spring-cloud-test-support</artifactId>
53+
<scope>test</scope>
54+
</dependency>
55+
<dependency>
56+
<groupId>org.testcontainers</groupId>
57+
<artifactId>testcontainers-junit-jupiter</artifactId>
58+
<scope>test</scope>
59+
</dependency>
60+
<dependency>
61+
<groupId>org.springframework.boot</groupId>
62+
<artifactId>spring-boot-testcontainers</artifactId>
63+
<scope>test</scope>
64+
</dependency>
65+
<dependency>
66+
<groupId>com.github.dasniko</groupId>
67+
<artifactId>testcontainers-keycloak</artifactId>
68+
<version>3.4.0</version>
69+
<scope>test</scope>
70+
</dependency>
71+
<dependency>
72+
<groupId>commons-io</groupId>
73+
<artifactId>commons-io</artifactId>
74+
<version>2.20.0</version>
75+
</dependency>
76+
<!-- MockServer for simulating a protected resource server -->
77+
<dependency>
78+
<groupId>org.mock-server</groupId>
79+
<artifactId>mockserver-netty</artifactId>
80+
<version>5.15.0</version>
81+
<scope>test</scope>
82+
</dependency>
83+
<dependency>
84+
<groupId>org.mock-server</groupId>
85+
<artifactId>mockserver-client-java</artifactId>
86+
<version>5.15.0</version>
87+
<scope>test</scope>
88+
</dependency>
89+
</dependencies>
90+
91+
<build>
92+
<plugins>
93+
<plugin>
94+
<!--skip deploy (this is just a test module) -->
95+
<artifactId>maven-deploy-plugin</artifactId>
96+
<configuration>
97+
<skip>true</skip>
98+
</configuration>
99+
</plugin>
100+
</plugins>
101+
</build>
102+
</project>
103+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
/*
2+
* Copyright 2013-present the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.cloud.config.client;
18+
19+
import java.net.URI;
20+
21+
import dasniko.testcontainers.keycloak.KeycloakContainer;
22+
import org.apache.commons.logging.Log;
23+
import org.apache.commons.logging.LogFactory;
24+
import org.junit.jupiter.api.AfterAll;
25+
import org.junit.jupiter.api.BeforeAll;
26+
import org.junit.jupiter.api.BeforeEach;
27+
import org.junit.jupiter.api.Tag;
28+
import org.junit.jupiter.api.Test;
29+
import org.junit.jupiter.api.TestInstance;
30+
import org.mockserver.client.MockServerClient;
31+
import org.mockserver.integration.ClientAndServer;
32+
import org.mockserver.model.HttpRequest;
33+
import org.mockserver.model.MediaType;
34+
import org.testcontainers.junit.jupiter.Container;
35+
import org.testcontainers.junit.jupiter.Testcontainers;
36+
37+
import org.springframework.boot.security.oauth2.client.autoconfigure.OAuth2ClientProperties;
38+
import org.springframework.http.ResponseEntity;
39+
import org.springframework.web.client.RestTemplate;
40+
41+
import static org.assertj.core.api.Assertions.assertThat;
42+
import static org.assertj.core.api.Assertions.assertThatThrownBy;
43+
import static org.mockserver.model.HttpRequest.request;
44+
import static org.mockserver.model.HttpResponse.response;
45+
46+
/**
47+
* IntegrationTest for OAuth2 support in ConfigClientRequestTemplateFactory using Keycloak
48+
* Test container as the Authorization Server and MockServer as a protected resource
49+
* server.
50+
*/
51+
@Tag("DockerRequired")
52+
@Testcontainers
53+
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
54+
class ConfigClientRequestTemplateFactoryOAuth2Tests {
55+
56+
private static final Log log = LogFactory.getLog(ConfigClientRequestTemplateFactoryOAuth2Tests.class);
57+
58+
@Container
59+
static KeycloakContainer keycloak = new KeycloakContainer().withRealmImportFile("test-realm.json"); // classpath
60+
// resource
61+
62+
private ClientAndServer mockServer;
63+
64+
private MockServerClient mockClient;
65+
66+
@BeforeAll
67+
void startMockServer() {
68+
mockServer = ClientAndServer.startClientAndServer(0);
69+
mockClient = new MockServerClient("localhost", mockServer.getLocalPort());
70+
}
71+
72+
@BeforeEach
73+
void resetExpectations() {
74+
mockClient.clear(request().withPath("/secure"));
75+
mockClient.when(request().withMethod("GET").withPath("/secure")).respond(request -> {
76+
if (request.containsHeader("Authorization")) {
77+
String authHeader = request.getFirstHeader("Authorization");
78+
if (authHeader != null && authHeader.startsWith("Bearer ")) {
79+
return response().withStatusCode(200).withContentType(MediaType.TEXT_PLAIN).withBody("ok");
80+
}
81+
}
82+
return response().withStatusCode(401);
83+
});
84+
}
85+
86+
@AfterAll
87+
void tearDown() {
88+
if (mockClient != null) {
89+
mockClient.close();
90+
}
91+
if (mockServer != null) {
92+
mockServer.stop();
93+
}
94+
}
95+
96+
@Test
97+
void restTemplateAddsBearerTokenFromKeycloakUsingClientCredentials() {
98+
// given OAuth2 client configuration pointing to Keycloak token endpoint
99+
String tokenUri = keycloak.getAuthServerUrl() + "/realms/test-realm/protocol/openid-connect/token";
100+
101+
ConfigClientProperties props = new ConfigClientProperties();
102+
props.getOauth2().setEnabled(true);
103+
104+
OAuth2ClientProperties.Provider provider = props.getOauth2().getProvider();
105+
provider.setTokenUri(tokenUri);
106+
107+
OAuth2ClientProperties.Registration registration = props.getOauth2().getRegistration();
108+
registration.setClientId("config-client");
109+
registration.setClientSecret("my-client-secret");
110+
registration.setAuthorizationGrantType("client_credentials");
111+
112+
ConfigClientRequestTemplateFactory factory = new ConfigClientRequestTemplateFactory(log, props);
113+
RestTemplate restTemplate = factory.create();
114+
115+
// when
116+
String url = "http://localhost:" + mockServer.getLocalPort() + "/secure";
117+
ResponseEntity<String> response = restTemplate.getForEntity(URI.create(url), String.class);
118+
119+
// then
120+
assertThat(response.getStatusCode().is2xxSuccessful()).isTrue();
121+
assertThat(response.getBody()).isEqualTo("ok");
122+
123+
HttpRequest[] recorded = mockClient.retrieveRecordedRequests(request().withPath("/secure"));
124+
assertThat(recorded).hasSize(1);
125+
String authHeader = recorded[0].getFirstHeader("Authorization");
126+
assertThat(authHeader).isNotNull().startsWith("Bearer ");
127+
}
128+
129+
@Test
130+
void restTemplateDoesNotAddAuthorizationHeaderWhenOauth2Disabled() {
131+
// given ConfigClientProperties with OAuth2 disabled
132+
ConfigClientProperties props = new ConfigClientProperties();
133+
props.getOauth2().setEnabled(false); // explicitly disabled
134+
135+
ConfigClientRequestTemplateFactory factory = new ConfigClientRequestTemplateFactory(log, props);
136+
RestTemplate restTemplate = factory.create();
137+
138+
// when
139+
String url = "http://localhost:" + mockServer.getLocalPort() + "/secure";
140+
141+
assertThatThrownBy(() -> restTemplate.getForEntity(URI.create(url), String.class))
142+
.isInstanceOf(org.springframework.web.client.HttpClientErrorException.Unauthorized.class);
143+
144+
// then
145+
146+
HttpRequest[] recorded = mockClient.retrieveRecordedRequests(request().withPath("/secure"));
147+
assertThat(recorded).hasSize(1); // only this test's request
148+
assertThat(recorded[0].containsHeader("Authorization")).isFalse();
149+
}
150+
151+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
{
2+
"realm": "test-realm",
3+
"enabled": true,
4+
"clients": [
5+
{
6+
"clientId": "config-client",
7+
"secret": "my-client-secret",
8+
"name": "Config Client",
9+
"protocol": "openid-connect",
10+
"publicClient": false,
11+
"directAccessGrantsEnabled": false,
12+
"serviceAccountsEnabled": true,
13+
"redirectUris": ["*"],
14+
"webOrigins": ["*"]
15+
}
16+
]
17+
}
18+

0 commit comments

Comments
 (0)