Skip to content

Commit f56386e

Browse files
committed
Merge pull request #17010 from nosan
* pr/17010: Polish "Use request factory to support Basic Authentication" Use request factory to support Basic Authentication Closes gh-17010
2 parents 4ac1407 + 76e075d commit f56386e

File tree

7 files changed

+324
-165
lines changed

7 files changed

+324
-165
lines changed

spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/web/client/TestRestTemplate.java

Lines changed: 23 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -17,16 +17,11 @@
1717
package org.springframework.boot.test.web.client;
1818

1919
import java.io.IOException;
20-
import java.lang.reflect.Field;
2120
import java.net.URI;
22-
import java.util.ArrayList;
2321
import java.util.Arrays;
24-
import java.util.Collections;
2522
import java.util.HashSet;
26-
import java.util.List;
2723
import java.util.Map;
2824
import java.util.Set;
29-
import java.util.function.Supplier;
3025

3126
import org.apache.http.client.HttpClient;
3227
import org.apache.http.client.config.CookieSpecs;
@@ -39,9 +34,6 @@
3934
import org.apache.http.protocol.HttpContext;
4035
import org.apache.http.ssl.SSLContextBuilder;
4136

42-
import org.springframework.beans.BeanInstantiationException;
43-
import org.springframework.beans.BeanUtils;
44-
import org.springframework.boot.web.client.ClientHttpRequestFactorySupplier;
4537
import org.springframework.boot.web.client.RestTemplateBuilder;
4638
import org.springframework.boot.web.client.RootUriTemplateHandler;
4739
import org.springframework.core.ParameterizedTypeReference;
@@ -51,13 +43,9 @@
5143
import org.springframework.http.RequestEntity;
5244
import org.springframework.http.ResponseEntity;
5345
import org.springframework.http.client.ClientHttpRequestFactory;
54-
import org.springframework.http.client.ClientHttpRequestInterceptor;
5546
import org.springframework.http.client.ClientHttpResponse;
5647
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
57-
import org.springframework.http.client.InterceptingClientHttpRequestFactory;
58-
import org.springframework.http.client.support.BasicAuthenticationInterceptor;
5948
import org.springframework.util.Assert;
60-
import org.springframework.util.ReflectionUtils;
6149
import org.springframework.web.client.DefaultResponseErrorHandler;
6250
import org.springframework.web.client.RequestCallback;
6351
import org.springframework.web.client.ResponseExtractor;
@@ -86,14 +74,17 @@
8674
* @author Phillip Webb
8775
* @author Andy Wilkinson
8876
* @author Kristine Jetzke
77+
* @author Dmytro Nosan
8978
* @since 1.4.0
9079
*/
9180
public class TestRestTemplate {
9281

93-
private final RestTemplate restTemplate;
82+
private final RestTemplateBuilder builder;
9483

9584
private final HttpClientOption[] httpClientOptions;
9685

86+
private final RestTemplate restTemplate;
87+
9788
/**
9889
* Create a new {@link TestRestTemplate} instance.
9990
* @param restTemplateBuilder builder used to configure underlying
@@ -125,60 +116,30 @@ public TestRestTemplate(String username, String password,
125116

126117
/**
127118
* Create a new {@link TestRestTemplate} instance with the specified credentials.
128-
* @param restTemplateBuilder builder used to configure underlying
129-
* {@link RestTemplate}
119+
* @param builder builder used to configure underlying {@link RestTemplate}
130120
* @param username the username to use (or {@code null})
131121
* @param password the password (or {@code null})
132122
* @param httpClientOptions client options to use if the Apache HTTP Client is used
133123
* @since 2.0.0
134124
*/
135-
public TestRestTemplate(RestTemplateBuilder restTemplateBuilder, String username,
136-
String password, HttpClientOption... httpClientOptions) {
137-
this((restTemplateBuilder != null) ? restTemplateBuilder.build() : null, username,
138-
password, httpClientOptions);
139-
}
140-
141-
private TestRestTemplate(RestTemplate restTemplate, String username, String password,
125+
public TestRestTemplate(RestTemplateBuilder builder, String username, String password,
142126
HttpClientOption... httpClientOptions) {
143-
Assert.notNull(restTemplate, "RestTemplate must not be null");
127+
Assert.notNull(builder, "Builder must not be null");
128+
this.builder = builder;
144129
this.httpClientOptions = httpClientOptions;
145-
if (getRequestFactoryClass(restTemplate)
146-
.isAssignableFrom(HttpComponentsClientHttpRequestFactory.class)) {
147-
restTemplate.setRequestFactory(
148-
new CustomHttpComponentsClientHttpRequestFactory(httpClientOptions));
149-
}
150-
addAuthentication(restTemplate, username, password);
151-
restTemplate.setErrorHandler(new NoOpResponseErrorHandler());
152-
this.restTemplate = restTemplate;
153-
}
154-
155-
private Class<? extends ClientHttpRequestFactory> getRequestFactoryClass(
156-
RestTemplate restTemplate) {
157-
ClientHttpRequestFactory requestFactory = restTemplate.getRequestFactory();
158-
if (InterceptingClientHttpRequestFactory.class
159-
.isAssignableFrom(requestFactory.getClass())) {
160-
Field requestFactoryField = ReflectionUtils.findField(RestTemplate.class,
161-
"requestFactory");
162-
ReflectionUtils.makeAccessible(requestFactoryField);
163-
requestFactory = (ClientHttpRequestFactory) ReflectionUtils
164-
.getField(requestFactoryField, restTemplate);
165-
}
166-
return requestFactory.getClass();
167-
}
168-
169-
private void addAuthentication(RestTemplate restTemplate, String username,
170-
String password) {
171-
if (username == null) {
172-
return;
130+
if (httpClientOptions != null) {
131+
ClientHttpRequestFactory requestFactory = builder.buildRequestFactory();
132+
if (requestFactory instanceof HttpComponentsClientHttpRequestFactory) {
133+
builder = builder.requestFactory(
134+
() -> new CustomHttpComponentsClientHttpRequestFactory(
135+
httpClientOptions));
136+
}
173137
}
174-
List<ClientHttpRequestInterceptor> interceptors = restTemplate.getInterceptors();
175-
if (interceptors == null) {
176-
interceptors = Collections.emptyList();
138+
if (username != null || password != null) {
139+
builder = builder.basicAuthentication(username, password);
177140
}
178-
interceptors = new ArrayList<>(interceptors);
179-
interceptors.removeIf(BasicAuthenticationInterceptor.class::isInstance);
180-
interceptors.add(new BasicAuthenticationInterceptor(username, password));
181-
restTemplate.setInterceptors(interceptors);
141+
this.restTemplate = builder.build();
142+
this.restTemplate.setErrorHandler(new NoOpResponseErrorHandler());
182143
}
183144

184145
/**
@@ -1035,25 +996,10 @@ public RestTemplate getRestTemplate() {
1035996
* @since 1.4.1
1036997
*/
1037998
public TestRestTemplate withBasicAuth(String username, String password) {
1038-
RestTemplate restTemplate = new RestTemplateBuilder()
1039-
.requestFactory(getRequestFactorySupplier())
1040-
.messageConverters(getRestTemplate().getMessageConverters())
1041-
.interceptors(getRestTemplate().getInterceptors())
1042-
.uriTemplateHandler(getRestTemplate().getUriTemplateHandler()).build();
1043-
return new TestRestTemplate(restTemplate, username, password,
999+
TestRestTemplate template = new TestRestTemplate(this.builder, username, password,
10441000
this.httpClientOptions);
1045-
}
1046-
1047-
private Supplier<ClientHttpRequestFactory> getRequestFactorySupplier() {
1048-
return () -> {
1049-
try {
1050-
return BeanUtils
1051-
.instantiateClass(getRequestFactoryClass(getRestTemplate()));
1052-
}
1053-
catch (BeanInstantiationException ex) {
1054-
return new ClientHttpRequestFactorySupplier().get();
1055-
}
1056-
};
1001+
template.setUriTemplateHandler(getRestTemplate().getUriTemplateHandler());
1002+
return template;
10571003
}
10581004

10591005
@SuppressWarnings({ "rawtypes", "unchecked" })
@@ -1075,7 +1021,7 @@ private URI applyRootUriIfNecessary(URI uri) {
10751021
}
10761022

10771023
/**
1078-
* Options used to customize the Apache Http Client if it is used.
1024+
* Options used to customize the Apache HTTP Client.
10791025
*/
10801026
public enum HttpClientOption {
10811027

spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/web/client/TestRestTemplateTests.java

Lines changed: 38 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import java.lang.reflect.Modifier;
2222
import java.net.URI;
2323
import java.util.List;
24+
import java.util.stream.Collectors;
2425

2526
import org.apache.http.client.config.RequestConfig;
2627
import org.junit.jupiter.api.Test;
@@ -35,12 +36,9 @@
3536
import org.springframework.http.RequestEntity;
3637
import org.springframework.http.client.ClientHttpRequest;
3738
import org.springframework.http.client.ClientHttpRequestFactory;
38-
import org.springframework.http.client.ClientHttpRequestInterceptor;
3939
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
40-
import org.springframework.http.client.InterceptingClientHttpRequestFactory;
4140
import org.springframework.http.client.OkHttp3ClientHttpRequestFactory;
4241
import org.springframework.http.client.SimpleClientHttpRequestFactory;
43-
import org.springframework.http.client.support.BasicAuthenticationInterceptor;
4442
import org.springframework.mock.env.MockEnvironment;
4543
import org.springframework.mock.http.client.MockClientHttpRequest;
4644
import org.springframework.mock.http.client.MockClientHttpResponse;
@@ -102,25 +100,11 @@ public void useTheSameRequestFactoryClassWithBasicAuth() {
102100
TestRestTemplate testRestTemplate = new TestRestTemplate(builder)
103101
.withBasicAuth("test", "test");
104102
RestTemplate restTemplate = testRestTemplate.getRestTemplate();
103+
assertThat(restTemplate.getRequestFactory().getClass().getName())
104+
.contains("BasicAuth");
105105
Object requestFactory = ReflectionTestUtils
106106
.getField(restTemplate.getRequestFactory(), "requestFactory");
107-
assertThat(requestFactory).isNotEqualTo(customFactory)
108-
.hasSameClassAs(customFactory);
109-
}
110-
111-
@Test
112-
public void withBasicAuthWhenRequestFactoryTypeCannotBeInstantiatedShouldFallback() {
113-
TestClientHttpRequestFactory customFactory = new TestClientHttpRequestFactory(
114-
"my-request-factory");
115-
RestTemplateBuilder builder = new RestTemplateBuilder()
116-
.requestFactory(() -> customFactory);
117-
TestRestTemplate testRestTemplate = new TestRestTemplate(builder)
118-
.withBasicAuth("test", "test");
119-
RestTemplate restTemplate = testRestTemplate.getRestTemplate();
120-
Object requestFactory = ReflectionTestUtils
121-
.getField(restTemplate.getRequestFactory(), "requestFactory");
122-
assertThat(requestFactory).isNotEqualTo(customFactory)
123-
.isInstanceOf(CustomHttpComponentsClientHttpRequestFactory.class);
107+
assertThat(requestFactory).isEqualTo(customFactory).hasSameClassAs(customFactory);
124108
}
125109

126110
@Test
@@ -148,9 +132,10 @@ public void getRootUriRootUriNotSet() {
148132

149133
@Test
150134
public void authenticated() {
151-
assertThat(new TestRestTemplate("user", "password").getRestTemplate()
152-
.getRequestFactory())
153-
.isInstanceOf(InterceptingClientHttpRequestFactory.class);
135+
RestTemplate restTemplate = new TestRestTemplate("user", "password")
136+
.getRestTemplate();
137+
ClientHttpRequestFactory factory = restTemplate.getRequestFactory();
138+
assertThat(factory.getClass().getName()).contains("BasicAuthentication");
154139
}
155140

156141
@Test
@@ -227,43 +212,39 @@ private Object mockArgument(Class<?> type) throws Exception {
227212
}
228213

229214
@Test
230-
public void withBasicAuthAddsBasicAuthInterceptorWhenNotAlreadyPresent() {
231-
TestRestTemplate originalTemplate = new TestRestTemplate();
232-
TestRestTemplate basicAuthTemplate = originalTemplate.withBasicAuth("user",
233-
"password");
234-
assertThat(basicAuthTemplate.getRestTemplate().getMessageConverters())
235-
.containsExactlyElementsOf(
236-
originalTemplate.getRestTemplate().getMessageConverters());
237-
assertThat(basicAuthTemplate.getRestTemplate().getRequestFactory())
238-
.isInstanceOf(InterceptingClientHttpRequestFactory.class);
215+
public void withBasicAuthAddsBasicAuthClientFactoryWhenNotAlreadyPresent() {
216+
TestRestTemplate original = new TestRestTemplate();
217+
TestRestTemplate basicAuth = original.withBasicAuth("user", "password");
218+
assertThat(getConverterClasses(original))
219+
.containsExactlyElementsOf(getConverterClasses(basicAuth));
220+
assertThat(basicAuth.getRestTemplate().getRequestFactory().getClass().getName())
221+
.contains("BasicAuth");
239222
assertThat(ReflectionTestUtils.getField(
240-
basicAuthTemplate.getRestTemplate().getRequestFactory(),
241-
"requestFactory"))
223+
basicAuth.getRestTemplate().getRequestFactory(), "requestFactory"))
242224
.isInstanceOf(CustomHttpComponentsClientHttpRequestFactory.class);
243-
assertThat(basicAuthTemplate.getRestTemplate().getUriTemplateHandler())
244-
.isSameAs(originalTemplate.getRestTemplate().getUriTemplateHandler());
245-
assertThat(basicAuthTemplate.getRestTemplate().getInterceptors()).hasSize(1);
246-
assertBasicAuthorizationInterceptorCredentials(basicAuthTemplate, "user",
247-
"password");
225+
assertThat(basicAuth.getRestTemplate().getInterceptors()).isEmpty();
226+
assertBasicAuthorizationCredentials(basicAuth, "user", "password");
248227
}
249228

250229
@Test
251-
public void withBasicAuthReplacesBasicAuthInterceptorWhenAlreadyPresent() {
230+
public void withBasicAuthReplacesBasicAuthClientFactoryWhenAlreadyPresent() {
252231
TestRestTemplate original = new TestRestTemplate("foo", "bar")
253232
.withBasicAuth("replace", "replace");
254233
TestRestTemplate basicAuth = original.withBasicAuth("user", "password");
255-
assertThat(basicAuth.getRestTemplate().getMessageConverters())
256-
.containsExactlyElementsOf(
257-
original.getRestTemplate().getMessageConverters());
258-
assertThat(basicAuth.getRestTemplate().getRequestFactory())
259-
.isInstanceOf(InterceptingClientHttpRequestFactory.class);
234+
assertThat(getConverterClasses(basicAuth))
235+
.containsExactlyElementsOf(getConverterClasses(original));
236+
assertThat(basicAuth.getRestTemplate().getRequestFactory().getClass().getName())
237+
.contains("BasicAuth");
260238
assertThat(ReflectionTestUtils.getField(
261239
basicAuth.getRestTemplate().getRequestFactory(), "requestFactory"))
262240
.isInstanceOf(CustomHttpComponentsClientHttpRequestFactory.class);
263-
assertThat(basicAuth.getRestTemplate().getUriTemplateHandler())
264-
.isSameAs(original.getRestTemplate().getUriTemplateHandler());
265-
assertThat(basicAuth.getRestTemplate().getInterceptors()).hasSize(1);
266-
assertBasicAuthorizationInterceptorCredentials(basicAuth, "user", "password");
241+
assertThat(basicAuth.getRestTemplate().getInterceptors()).isEmpty();
242+
assertBasicAuthorizationCredentials(basicAuth, "user", "password");
243+
}
244+
245+
private List<Class<?>> getConverterClasses(TestRestTemplate testRestTemplate) {
246+
return testRestTemplate.getRestTemplate().getMessageConverters().stream()
247+
.map(Object::getClass).collect(Collectors.toList());
267248
}
268249

269250
@Test
@@ -394,17 +375,14 @@ private void verifyRelativeUriHandling(TestRestTemplateCallback callback)
394375
verify(requestFactory).createRequest(eq(absoluteUri), any(HttpMethod.class));
395376
}
396377

397-
private void assertBasicAuthorizationInterceptorCredentials(
398-
TestRestTemplate testRestTemplate, String username, String password) {
399-
@SuppressWarnings("unchecked")
400-
List<ClientHttpRequestInterceptor> requestFactoryInterceptors = (List<ClientHttpRequestInterceptor>) ReflectionTestUtils
401-
.getField(testRestTemplate.getRestTemplate().getRequestFactory(),
402-
"interceptors");
403-
assertThat(requestFactoryInterceptors).hasSize(1);
404-
ClientHttpRequestInterceptor interceptor = requestFactoryInterceptors.get(0);
405-
assertThat(interceptor).isInstanceOf(BasicAuthenticationInterceptor.class);
406-
assertThat(interceptor).hasFieldOrPropertyWithValue("username", username);
407-
assertThat(interceptor).hasFieldOrPropertyWithValue("password", password);
378+
private void assertBasicAuthorizationCredentials(TestRestTemplate testRestTemplate,
379+
String username, String password) {
380+
ClientHttpRequestFactory requestFactory = testRestTemplate.getRestTemplate()
381+
.getRequestFactory();
382+
Object authentication = ReflectionTestUtils.getField(requestFactory,
383+
"authentication");
384+
assertThat(authentication).hasFieldOrPropertyWithValue("username", username);
385+
assertThat(authentication).hasFieldOrPropertyWithValue("password", password);
408386

409387
}
410388

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
/*
2+
* Copyright 2012-2019 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.boot.web.client;
18+
19+
import java.nio.charset.Charset;
20+
21+
import org.springframework.http.HttpHeaders;
22+
import org.springframework.http.client.ClientHttpRequest;
23+
import org.springframework.util.Assert;
24+
25+
/**
26+
* Basic authentication properties which are used by
27+
* {@link BasicAuthenticationClientHttpRequestFactory}.
28+
*
29+
* @author Dmytro Nosan
30+
* @see BasicAuthenticationClientHttpRequestFactory
31+
*/
32+
class BasicAuthentication {
33+
34+
private final String username;
35+
36+
private final String password;
37+
38+
private final Charset charset;
39+
40+
BasicAuthentication(String username, String password, Charset charset) {
41+
Assert.notNull(username, "Username must not be null");
42+
Assert.notNull(password, "Password must not be null");
43+
this.username = username;
44+
this.password = password;
45+
this.charset = charset;
46+
}
47+
48+
void applyTo(ClientHttpRequest request) {
49+
HttpHeaders headers = request.getHeaders();
50+
if (!headers.containsKey(HttpHeaders.AUTHORIZATION)) {
51+
headers.setBasicAuth(this.username, this.password, this.charset);
52+
}
53+
}
54+
55+
}

0 commit comments

Comments
 (0)