diff --git a/sdk/spring/CHANGELOG.md b/sdk/spring/CHANGELOG.md index 699499028454..e172ea6b3d93 100644 --- a/sdk/spring/CHANGELOG.md +++ b/sdk/spring/CHANGELOG.md @@ -5,6 +5,7 @@ Upgrade Spring Boot dependencies version to 2.7.4 and Spring Cloud dependencies #### Bugs Fixed - Fix bug: Put a value into Collections.emptyMap(). [#31190](https://github.com/Azure/azure-sdk-for-java/issues/31190). +- Fix bug: RestTemplate used to get access token should only contain 2 converters. [#31482](https://github.com/Azure/azure-sdk-for-java/issues/31482). - Fix bug: RestOperations is not well configured when jwkResolver is null. [#31218](https://github.com/Azure/azure-sdk-for-java/issues/31218). - Fix bug: Duplicated "scope" parameter. [#31191](https://github.com/Azure/azure-sdk-for-java/issues/31191). diff --git a/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/aad/implementation/AadRestTemplateCreator.java b/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/aad/implementation/AadRestTemplateCreator.java index 6cf836892cba..314f9742f8e7 100644 --- a/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/aad/implementation/AadRestTemplateCreator.java +++ b/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/aad/implementation/AadRestTemplateCreator.java @@ -5,14 +5,11 @@ import org.springframework.boot.web.client.RestTemplateBuilder; import org.springframework.http.converter.FormHttpMessageConverter; -import org.springframework.http.converter.HttpMessageConverter; import org.springframework.security.oauth2.client.http.OAuth2ErrorResponseErrorHandler; import org.springframework.security.oauth2.core.http.converter.OAuth2AccessTokenResponseHttpMessageConverter; import org.springframework.util.Assert; import org.springframework.web.client.RestTemplate; -import java.util.List; - /** * Util class used to create {@link RestTemplate}s for all Azure AD related http requests. */ @@ -27,25 +24,14 @@ public static RestTemplate createRestTemplate(RestTemplateBuilder builder) { } public static RestTemplate createOAuth2ErrorResponseHandledRestTemplate(RestTemplateBuilder builder) { - RestTemplate restTemplate = createRestTemplate(builder); - restTemplate.setErrorHandler(new OAuth2ErrorResponseErrorHandler()); - return restTemplate; + builder = builder.errorHandler(new OAuth2ErrorResponseErrorHandler()); + return createRestTemplate(builder); } public static RestTemplate createOAuth2AccessTokenResponseClientRestTemplate(RestTemplateBuilder builder) { - RestTemplate restTemplate = createOAuth2ErrorResponseHandledRestTemplate(builder); - List> converters = restTemplate.getMessageConverters(); - if (notContainsElementOfType(converters, FormHttpMessageConverter.class)) { - converters.add(new FormHttpMessageConverter()); - } - if (notContainsElementOfType(converters, OAuth2AccessTokenResponseHttpMessageConverter.class)) { - converters.add(new OAuth2AccessTokenResponseHttpMessageConverter()); - } - return restTemplate; - } - - private static boolean notContainsElementOfType(List list, Class clazz) { - return list.stream().noneMatch(item -> item.getClass().equals(clazz)); + builder = builder.messageConverters( + new FormHttpMessageConverter(), new OAuth2AccessTokenResponseHttpMessageConverter()); + return createOAuth2ErrorResponseHandledRestTemplate(builder); } } diff --git a/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/aad/implementation/AadRestTemplateCreatorTest.java b/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/aad/implementation/AadRestTemplateCreatorTest.java index eb4e062e150c..edb7f2edf501 100644 --- a/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/aad/implementation/AadRestTemplateCreatorTest.java +++ b/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/aad/implementation/AadRestTemplateCreatorTest.java @@ -7,24 +7,39 @@ import org.springframework.boot.autoconfigure.web.client.RestTemplateAutoConfiguration; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.boot.web.client.RestTemplateBuilder; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.RequestEntity; import org.springframework.http.converter.FormHttpMessageConverter; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.security.oauth2.client.http.OAuth2ErrorResponseErrorHandler; +import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse; import org.springframework.security.oauth2.core.http.converter.OAuth2AccessTokenResponseHttpMessageConverter; +import org.springframework.test.web.client.ExpectedCount; +import org.springframework.test.web.client.MockRestServiceServer; import org.springframework.web.client.ResponseErrorHandler; import org.springframework.web.client.RestTemplate; +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Paths; import java.util.List; import static com.azure.spring.cloud.autoconfigure.aad.implementation.AadRestTemplateCreator.createOAuth2AccessTokenResponseClientRestTemplate; import static com.azure.spring.cloud.autoconfigure.aad.implementation.AadRestTemplateCreator.createOAuth2ErrorResponseHandledRestTemplate; -import static com.azure.spring.cloud.autoconfigure.aad.implementation.AadRestTemplateCreator.createRestTemplate; import static com.azure.spring.cloud.autoconfigure.aad.implementation.RestTemplateProxyCustomizerConfiguration.FACTORY; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.hasItem; +import static org.hamcrest.Matchers.isA; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertSame; -import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo; +import static org.springframework.test.web.client.response.MockRestResponseCreators.withStatus; class AadRestTemplateCreatorTest { @@ -34,28 +49,56 @@ void testAadRestOperationConfiguration() { .withUserConfiguration(RestTemplateAutoConfiguration.class) .run((context) -> { RestTemplateBuilder builder = context.getBean(RestTemplateBuilder.class); + testCreateOAuth2ErrorResponseHandledRestTemplate(builder); + testCreateOAuth2AccessTokenResponseClientRestTemplate(builder); + }); + } - RestTemplate restTemplate = createRestTemplate(builder); - ResponseErrorHandler handler = restTemplate.getErrorHandler(); - assertNotEquals(handler.getClass(), OAuth2ErrorResponseErrorHandler.class); - List> converters = restTemplate.getMessageConverters(); - assertFalse(hasItemOfClass(converters, FormHttpMessageConverter.class)); - assertFalse(hasItemOfClass(converters, OAuth2AccessTokenResponseHttpMessageConverter.class)); + private void testCreateOAuth2ErrorResponseHandledRestTemplate(RestTemplateBuilder builder) { + RestTemplate restTemplate = createOAuth2ErrorResponseHandledRestTemplate(builder); + ResponseErrorHandler handler = restTemplate.getErrorHandler(); + assertEquals(OAuth2ErrorResponseErrorHandler.class, handler.getClass()); + } - restTemplate = createOAuth2ErrorResponseHandledRestTemplate(builder); - handler = restTemplate.getErrorHandler(); - assertEquals(handler.getClass(), OAuth2ErrorResponseErrorHandler.class); - converters = restTemplate.getMessageConverters(); - assertFalse(hasItemOfClass(converters, FormHttpMessageConverter.class)); - assertFalse(hasItemOfClass(converters, OAuth2AccessTokenResponseHttpMessageConverter.class)); + private void testCreateOAuth2AccessTokenResponseClientRestTemplate(RestTemplateBuilder builder) { + RestTemplate restTemplate = createOAuth2AccessTokenResponseClientRestTemplate(builder); + ResponseErrorHandler handler = restTemplate.getErrorHandler(); + assertEquals(OAuth2ErrorResponseErrorHandler.class, handler.getClass()); + List> converters = restTemplate.getMessageConverters(); + assertEquals(2, converters.size()); + assertThat(converters, hasItem(isA(FormHttpMessageConverter.class))); + assertThat(converters, hasItem(isA(OAuth2AccessTokenResponseHttpMessageConverter.class))); - restTemplate = createOAuth2AccessTokenResponseClientRestTemplate(builder); - handler = restTemplate.getErrorHandler(); - assertEquals(handler.getClass(), OAuth2ErrorResponseErrorHandler.class); - converters = restTemplate.getMessageConverters(); - assertTrue(hasItemOfClass(converters, FormHttpMessageConverter.class)); - assertTrue(hasItemOfClass(converters, OAuth2AccessTokenResponseHttpMessageConverter.class)); - }); + testOAuth2AccessTokenResponseCanBeConstructed(restTemplate); + } + + private void testOAuth2AccessTokenResponseCanBeConstructed(RestTemplate restTemplate) { + URI url; + try { + url = new URI("https://login.microsoftonline.comv/common/oauth2/v2.0/token"); + } catch (URISyntaxException e) { + throw new IllegalStateException(e); + } + MockRestServiceServer mockServer = MockRestServiceServer.createServer(restTemplate); + mockServer + .expect(ExpectedCount.once(), requestTo(url)) + .andRespond(withStatus(HttpStatus.OK) + .contentType(MediaType.APPLICATION_JSON) + .body(readAccessTokenResponse())); + OAuth2AccessTokenResponse response = restTemplate + .exchange(new RequestEntity<>(HttpMethod.POST, url), OAuth2AccessTokenResponse.class) + .getBody(); + assertNotNull(response); + assertEquals("test_access_token_value", response.getAccessToken().getTokenValue()); + } + + private String readAccessTokenResponse() { + try { + return new String(Files.readAllBytes( + Paths.get("src/test/resources/aad/access-token-response.json")), StandardCharsets.UTF_8); + } catch (IOException e) { + throw new IllegalStateException(e); + } } @Test @@ -70,9 +113,4 @@ void testRestOperationProxyConfiguration() { }); } - static boolean hasItemOfClass(List list, Class clazz) { - return list.stream() - .anyMatch(item -> item.getClass().equals(clazz)); - } - } diff --git a/sdk/spring/spring-cloud-azure-autoconfigure/src/test/resources/aad/access-token-response.json b/sdk/spring/spring-cloud-azure-autoconfigure/src/test/resources/aad/access-token-response.json new file mode 100644 index 000000000000..06366c914611 --- /dev/null +++ b/sdk/spring/spring-cloud-azure-autoconfigure/src/test/resources/aad/access-token-response.json @@ -0,0 +1,9 @@ +{ + "token_type": "Bearer", + "scope": "openid profile email", + "expires_in": 5139, + "ext_expires_in": 5139, + "access_token": "test_access_token_value", + "refresh_token": "test_refresh_token_value", + "id_token": "test_id_token_value" +} \ No newline at end of file