Use request factory to support Basic Authentication

Update `RestTemplateBuilder` to use a custom request factory to add
authentication headers rather than an interceptor.

Prior to this commit, the use of the `BasicAuthenticationInterceptor`
interceptor could cause `OutOfMemoryError` whenever a large file is
uploaded.

See gh-17010
This commit is contained in:
Dmytro Nosan
2019-05-29 18:50:52 +03:00
committed by Phillip Webb
parent 4ac1407af1
commit 18a0a7a2e8
7 changed files with 331 additions and 76 deletions

View File

@@ -19,11 +19,8 @@ package org.springframework.boot.test.web.client;
import java.io.IOException;
import java.lang.reflect.Field;
import java.net.URI;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Supplier;
@@ -41,6 +38,8 @@ import org.apache.http.ssl.SSLContextBuilder;
import org.springframework.beans.BeanInstantiationException;
import org.springframework.beans.BeanUtils;
import org.springframework.boot.web.client.BasicAuthentication;
import org.springframework.boot.web.client.BasicAuthenticationClientHttpRequestFactory;
import org.springframework.boot.web.client.ClientHttpRequestFactorySupplier;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.boot.web.client.RootUriTemplateHandler;
@@ -50,12 +49,11 @@ import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.RequestEntity;
import org.springframework.http.ResponseEntity;
import org.springframework.http.client.AbstractClientHttpRequestFactoryWrapper;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.http.client.InterceptingClientHttpRequestFactory;
import org.springframework.http.client.support.BasicAuthenticationInterceptor;
import org.springframework.util.Assert;
import org.springframework.util.ReflectionUtils;
import org.springframework.web.client.DefaultResponseErrorHandler;
@@ -86,6 +84,7 @@ import org.springframework.web.util.UriTemplateHandler;
* @author Phillip Webb
* @author Andy Wilkinson
* @author Kristine Jetzke
* @author Dmytro Nosan
* @since 1.4.0
*/
public class TestRestTemplate {
@@ -154,31 +153,35 @@ public class TestRestTemplate {
private Class<? extends ClientHttpRequestFactory> getRequestFactoryClass(
RestTemplate restTemplate) {
return getRequestFactory(restTemplate).getClass();
}
private ClientHttpRequestFactory getRequestFactory(RestTemplate restTemplate) {
ClientHttpRequestFactory requestFactory = restTemplate.getRequestFactory();
if (InterceptingClientHttpRequestFactory.class
.isAssignableFrom(requestFactory.getClass())) {
Field requestFactoryField = ReflectionUtils.findField(RestTemplate.class,
"requestFactory");
ReflectionUtils.makeAccessible(requestFactoryField);
requestFactory = (ClientHttpRequestFactory) ReflectionUtils
.getField(requestFactoryField, restTemplate);
while (requestFactory instanceof InterceptingClientHttpRequestFactory
|| requestFactory instanceof BasicAuthenticationClientHttpRequestFactory) {
requestFactory = unwrapRequestFactory(
((AbstractClientHttpRequestFactoryWrapper) requestFactory));
}
return requestFactory.getClass();
return requestFactory;
}
private ClientHttpRequestFactory unwrapRequestFactory(
AbstractClientHttpRequestFactoryWrapper requestFactory) {
Field field = ReflectionUtils.findField(
AbstractClientHttpRequestFactoryWrapper.class, "requestFactory");
ReflectionUtils.makeAccessible(field);
return (ClientHttpRequestFactory) ReflectionUtils.getField(field, requestFactory);
}
private void addAuthentication(RestTemplate restTemplate, String username,
String password) {
if (username == null) {
if (username == null || password == null) {
return;
}
List<ClientHttpRequestInterceptor> interceptors = restTemplate.getInterceptors();
if (interceptors == null) {
interceptors = Collections.emptyList();
}
interceptors = new ArrayList<>(interceptors);
interceptors.removeIf(BasicAuthenticationInterceptor.class::isInstance);
interceptors.add(new BasicAuthenticationInterceptor(username, password));
restTemplate.setInterceptors(interceptors);
ClientHttpRequestFactory requestFactory = getRequestFactory(restTemplate);
restTemplate.setRequestFactory(new BasicAuthenticationClientHttpRequestFactory(
new BasicAuthentication(username, password), requestFactory));
}
/**

View File

@@ -20,13 +20,13 @@ import java.io.IOException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.net.URI;
import java.util.List;
import org.apache.http.client.config.RequestConfig;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.web.client.TestRestTemplate.CustomHttpComponentsClientHttpRequestFactory;
import org.springframework.boot.test.web.client.TestRestTemplate.HttpClientOption;
import org.springframework.boot.web.client.BasicAuthenticationClientHttpRequestFactory;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpEntity;
@@ -35,12 +35,9 @@ import org.springframework.http.HttpStatus;
import org.springframework.http.RequestEntity;
import org.springframework.http.client.ClientHttpRequest;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.http.client.InterceptingClientHttpRequestFactory;
import org.springframework.http.client.OkHttp3ClientHttpRequestFactory;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.http.client.support.BasicAuthenticationInterceptor;
import org.springframework.mock.env.MockEnvironment;
import org.springframework.mock.http.client.MockClientHttpRequest;
import org.springframework.mock.http.client.MockClientHttpResponse;
@@ -150,7 +147,7 @@ public class TestRestTemplateTests {
public void authenticated() {
assertThat(new TestRestTemplate("user", "password").getRestTemplate()
.getRequestFactory())
.isInstanceOf(InterceptingClientHttpRequestFactory.class);
.isInstanceOf(BasicAuthenticationClientHttpRequestFactory.class);
}
@Test
@@ -227,7 +224,7 @@ public class TestRestTemplateTests {
}
@Test
public void withBasicAuthAddsBasicAuthInterceptorWhenNotAlreadyPresent() {
public void withBasicAuthAddsBasicAuthClientFactoryWhenNotAlreadyPresent() {
TestRestTemplate originalTemplate = new TestRestTemplate();
TestRestTemplate basicAuthTemplate = originalTemplate.withBasicAuth("user",
"password");
@@ -235,20 +232,19 @@ public class TestRestTemplateTests {
.containsExactlyElementsOf(
originalTemplate.getRestTemplate().getMessageConverters());
assertThat(basicAuthTemplate.getRestTemplate().getRequestFactory())
.isInstanceOf(InterceptingClientHttpRequestFactory.class);
.isInstanceOf(BasicAuthenticationClientHttpRequestFactory.class);
assertThat(ReflectionTestUtils.getField(
basicAuthTemplate.getRestTemplate().getRequestFactory(),
"requestFactory"))
.isInstanceOf(CustomHttpComponentsClientHttpRequestFactory.class);
assertThat(basicAuthTemplate.getRestTemplate().getUriTemplateHandler())
.isSameAs(originalTemplate.getRestTemplate().getUriTemplateHandler());
assertThat(basicAuthTemplate.getRestTemplate().getInterceptors()).hasSize(1);
assertBasicAuthorizationInterceptorCredentials(basicAuthTemplate, "user",
"password");
assertThat(basicAuthTemplate.getRestTemplate().getInterceptors()).isEmpty();
assertBasicAuthorizationCredentials(basicAuthTemplate, "user", "password");
}
@Test
public void withBasicAuthReplacesBasicAuthInterceptorWhenAlreadyPresent() {
public void withBasicAuthReplacesBasicAuthClientFactoryWhenAlreadyPresent() {
TestRestTemplate original = new TestRestTemplate("foo", "bar")
.withBasicAuth("replace", "replace");
TestRestTemplate basicAuth = original.withBasicAuth("user", "password");
@@ -256,14 +252,14 @@ public class TestRestTemplateTests {
.containsExactlyElementsOf(
original.getRestTemplate().getMessageConverters());
assertThat(basicAuth.getRestTemplate().getRequestFactory())
.isInstanceOf(InterceptingClientHttpRequestFactory.class);
.isInstanceOf(BasicAuthenticationClientHttpRequestFactory.class);
assertThat(ReflectionTestUtils.getField(
basicAuth.getRestTemplate().getRequestFactory(), "requestFactory"))
.isInstanceOf(CustomHttpComponentsClientHttpRequestFactory.class);
assertThat(basicAuth.getRestTemplate().getUriTemplateHandler())
.isSameAs(original.getRestTemplate().getUriTemplateHandler());
assertThat(basicAuth.getRestTemplate().getInterceptors()).hasSize(1);
assertBasicAuthorizationInterceptorCredentials(basicAuth, "user", "password");
assertThat(basicAuth.getRestTemplate().getInterceptors()).isEmpty();
assertBasicAuthorizationCredentials(basicAuth, "user", "password");
}
@Test
@@ -394,17 +390,14 @@ public class TestRestTemplateTests {
verify(requestFactory).createRequest(eq(absoluteUri), any(HttpMethod.class));
}
private void assertBasicAuthorizationInterceptorCredentials(
TestRestTemplate testRestTemplate, String username, String password) {
@SuppressWarnings("unchecked")
List<ClientHttpRequestInterceptor> requestFactoryInterceptors = (List<ClientHttpRequestInterceptor>) ReflectionTestUtils
.getField(testRestTemplate.getRestTemplate().getRequestFactory(),
"interceptors");
assertThat(requestFactoryInterceptors).hasSize(1);
ClientHttpRequestInterceptor interceptor = requestFactoryInterceptors.get(0);
assertThat(interceptor).isInstanceOf(BasicAuthenticationInterceptor.class);
assertThat(interceptor).hasFieldOrPropertyWithValue("username", username);
assertThat(interceptor).hasFieldOrPropertyWithValue("password", password);
private void assertBasicAuthorizationCredentials(TestRestTemplate testRestTemplate,
String username, String password) {
ClientHttpRequestFactory requestFactory = testRestTemplate.getRestTemplate()
.getRequestFactory();
Object authentication = ReflectionTestUtils.getField(requestFactory,
"authentication");
assertThat(authentication).hasFieldOrPropertyWithValue("username", username);
assertThat(authentication).hasFieldOrPropertyWithValue("password", password);
}