diff --git a/spring-cloud-netflix-eureka-client/src/main/java/org/springframework/cloud/netflix/eureka/http/WebClientTransportClientFactory.java b/spring-cloud-netflix-eureka-client/src/main/java/org/springframework/cloud/netflix/eureka/http/WebClientTransportClientFactory.java index 5a4a20cd7..9a96b25a8 100644 --- a/spring-cloud-netflix-eureka-client/src/main/java/org/springframework/cloud/netflix/eureka/http/WebClientTransportClientFactory.java +++ b/spring-cloud-netflix-eureka-client/src/main/java/org/springframework/cloud/netflix/eureka/http/WebClientTransportClientFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2017-2022 the original author or authors. + * Copyright 2017-2024 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. @@ -50,6 +50,7 @@ import org.springframework.web.reactive.function.client.ExchangeFilterFunction; import org.springframework.web.reactive.function.client.ExchangeFilterFunctions; import org.springframework.web.reactive.function.client.WebClient; +import org.springframework.web.util.UriComponentsBuilder; /** * Provides the custom {@link WebClient.Builder} required by the @@ -58,6 +59,7 @@ * * @author Daniel Lavoie * @author Haytham Mohamed + * @author Armin Krezovic */ public class WebClientTransportClientFactory implements TransportClientFactory { @@ -78,14 +80,14 @@ public EurekaHttpClient newClient(EurekaEndpoint endpoint) { } private WebClient.Builder setUrl(WebClient.Builder builder, String serviceUrl) { - String url = serviceUrl; + String url = UriComponentsBuilder.fromUriString(serviceUrl).userInfo(null).toUriString(); + try { URI serviceURI = new URI(serviceUrl); if (serviceURI.getUserInfo() != null) { String[] credentials = serviceURI.getUserInfo().split(":"); if (credentials.length == 2) { builder.filter(ExchangeFilterFunctions.basicAuthentication(credentials[0], credentials[1])); - url = serviceUrl.replace(credentials[0] + ":" + credentials[1] + "@", ""); } } } diff --git a/spring-cloud-netflix-eureka-client/src/test/java/org/springframework/cloud/netflix/eureka/http/WebClientTransportClientFactoryTest.java b/spring-cloud-netflix-eureka-client/src/test/java/org/springframework/cloud/netflix/eureka/http/WebClientTransportClientFactoryTest.java index b7d2d17ba..469d83911 100644 --- a/spring-cloud-netflix-eureka-client/src/test/java/org/springframework/cloud/netflix/eureka/http/WebClientTransportClientFactoryTest.java +++ b/spring-cloud-netflix-eureka-client/src/test/java/org/springframework/cloud/netflix/eureka/http/WebClientTransportClientFactoryTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2017-2022 the original author or authors. + * Copyright 2017-2024 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. @@ -16,23 +16,57 @@ package org.springframework.cloud.netflix.eureka.http; +import java.time.Duration; + import com.netflix.discovery.shared.resolver.DefaultEndpoint; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; +import reactor.core.publisher.Mono; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.web.reactive.function.client.ClientRequest; +import org.springframework.web.reactive.function.client.ClientResponse; +import org.springframework.web.reactive.function.client.ExchangeFunction; import org.springframework.web.reactive.function.client.WebClient; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + /** * @author Daniel Lavoie + * @author Armin Krezovic */ +@MockitoSettings(strictness = Strictness.LENIENT) class WebClientTransportClientFactoryTest { + @Mock + private ExchangeFunction exchangeFunction; + + @Captor + private ArgumentCaptor captor; + private WebClientTransportClientFactory transportClientFatory; @BeforeEach void setup() { - transportClientFatory = new WebClientTransportClientFactory(WebClient::builder); + ClientResponse mockResponse = mock(); + when(mockResponse.statusCode()).thenReturn(HttpStatus.OK); + when(mockResponse.bodyToMono(Void.class)).thenReturn(Mono.empty()); + given(exchangeFunction.exchange(captor.capture())).willReturn(Mono.just(mockResponse)); + + transportClientFatory = new WebClientTransportClientFactory( + () -> WebClient.builder().exchangeFunction(exchangeFunction)); } @Test @@ -45,6 +79,23 @@ void testInvalidUserInfo() { transportClientFatory.newClient(new DefaultEndpoint("http://test@localhost:8761")); } + @Test + void testUserInfoWithEncodedCharacters() { + String encodedBasicAuth = HttpHeaders.encodeBasicAuth("test", "MyPassword@", null); + String expectedAuthHeader = "Basic " + encodedBasicAuth; + String expectedUrl = "http://localhost:8761"; + + WebClientEurekaHttpClient client = (WebClientEurekaHttpClient) transportClientFatory + .newClient(new DefaultEndpoint("http://test:MyPassword%40@localhost:8761")); + + client.getWebClient().get().retrieve().bodyToMono(Void.class).block(Duration.ofSeconds(10)); + + ClientRequest request = verifyAndGetRequest(); + + assertThat(request.headers().getFirst(HttpHeaders.AUTHORIZATION)).isEqualTo(expectedAuthHeader); + assertThat(request.url().toString()).isEqualTo(expectedUrl); + } + @Test void testUserInfo() { transportClientFatory.newClient(new DefaultEndpoint("http://test:test@localhost:8761")); @@ -55,4 +106,11 @@ void shutdown() { transportClientFatory.shutdown(); } + private ClientRequest verifyAndGetRequest() { + ClientRequest request = captor.getValue(); + verify(exchangeFunction).exchange(request); + verifyNoMoreInteractions(exchangeFunction); + return request; + } + }