From 7f8d2042e328679d710f7be8655bb4f289e0ed46 Mon Sep 17 00:00:00 2001 From: Rujun Chen Date: Fri, 21 Oct 2022 17:01:11 +0800 Subject: [PATCH 1/8] Fix #31190. Put a value into Collections.emptyMap() --- sdk/spring/CHANGELOG.md | 11 + .../jackson/SerializerUtils.java | 18 +- ...ssionOAuth2AuthorizedClientRepository.java | 22 +- ...nOAuth2AuthorizedClientRepositoryTest.java | 240 ++++++++++++++++++ 4 files changed, 277 insertions(+), 14 deletions(-) create mode 100644 sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/aad/implementation/oauth2/JacksonHttpSessionOAuth2AuthorizedClientRepositoryTest.java diff --git a/sdk/spring/CHANGELOG.md b/sdk/spring/CHANGELOG.md index e7537ffe003f..c94e61142508 100644 --- a/sdk/spring/CHANGELOG.md +++ b/sdk/spring/CHANGELOG.md @@ -1,5 +1,16 @@ # Release History +## 4.4.0 (Unreleased) + +#### 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). +- Fix bug: NimbusJwtDecoder still uses `RestTemplate()` instead `RestTemplateBuilder` [#31233](https://github.com/Azure/azure-sdk-for-java/issues/31233) +- Fix bug: Proxy setting not work in Azure AD B2C web application [31593](https://github.com/Azure/azure-sdk-for-java/issues/31593) + + ## 4.4.0 (2022-09-26) Upgrade Spring Boot dependencies version to 2.7.3 and Spring Cloud dependencies version to 2021.0.3 Upgrade Spring Boot dependencies version to 2.7.2 and Spring Cloud dependencies version to 2021.0.3. diff --git a/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/aad/implementation/jackson/SerializerUtils.java b/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/aad/implementation/jackson/SerializerUtils.java index f5ba0fde806c..f2647ce320e2 100644 --- a/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/aad/implementation/jackson/SerializerUtils.java +++ b/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/aad/implementation/jackson/SerializerUtils.java @@ -10,14 +10,14 @@ import org.springframework.security.oauth2.client.OAuth2AuthorizedClient; import org.springframework.security.oauth2.client.jackson2.OAuth2ClientJackson2Module; -import java.util.HashMap; +import java.util.Collections; import java.util.Map; public final class SerializerUtils { private static final ObjectMapper OBJECT_MAPPER; private static final TypeReference> TYPE_REFERENCE = - new TypeReference>() { - }; + new TypeReference>() { + }; static { OBJECT_MAPPER = new ObjectMapper(); @@ -33,6 +33,11 @@ public final class SerializerUtils { private SerializerUtils() { } + /** + * Serialize {@link Map} to {@link String}. + * @param authorizedClients the map to be serialized. It will not be modified in this method. + * @return The serialized {@link String}. + */ public static String serializeOAuth2AuthorizedClientMap(Map authorizedClients) { String result; try { @@ -43,9 +48,14 @@ public static String serializeOAuth2AuthorizedClientMap(Map deserializeOAuth2AuthorizedClientMap(String authorizedClientsString) { if (authorizedClientsString == null) { - return new HashMap<>(); + return Collections.emptyMap(); } Map authorizedClients; try { diff --git a/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/aad/implementation/oauth2/JacksonHttpSessionOAuth2AuthorizedClientRepository.java b/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/aad/implementation/oauth2/JacksonHttpSessionOAuth2AuthorizedClientRepository.java index 4516ec6e1151..b2f8445e974a 100644 --- a/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/aad/implementation/oauth2/JacksonHttpSessionOAuth2AuthorizedClientRepository.java +++ b/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/aad/implementation/oauth2/JacksonHttpSessionOAuth2AuthorizedClientRepository.java @@ -13,6 +13,7 @@ import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import java.util.Collections; +import java.util.HashMap; import java.util.Map; import java.util.Optional; @@ -21,14 +22,14 @@ /** * An implementation of an {@link OAuth2AuthorizedClientRepository} that stores {@link OAuth2AuthorizedClient}'s in the * {@code HttpSession}. To make it compatible with different spring versions. Refs: - * https://github.com/spring-projects/spring-security/issues/9204 + * spring-security/issues/9204 * * @see OAuth2AuthorizedClientRepository * @see OAuth2AuthorizedClient */ public class JacksonHttpSessionOAuth2AuthorizedClientRepository implements OAuth2AuthorizedClientRepository { private static final String AUTHORIZED_CLIENTS_ATTR_NAME = - JacksonHttpSessionOAuth2AuthorizedClientRepository.class.getName() + ".AUTHORIZED_CLIENTS"; + JacksonHttpSessionOAuth2AuthorizedClientRepository.class.getName() + ".AUTHORIZED_CLIENTS"; private static final String MSG_REQUEST_CANNOT_BE_NULL = "request cannot be null"; @@ -48,10 +49,11 @@ public void saveAuthorizedClient(OAuth2AuthorizedClient authorizedClient, Authen Assert.notNull(authorizedClient, "authorizedClient cannot be null"); Assert.notNull(request, MSG_REQUEST_CANNOT_BE_NULL); Assert.notNull(response, "response cannot be null"); - Map authorizedClients = this.getAuthorizedClients(request); + Map authorizedClients = + new HashMap<>(this.getAuthorizedClients(request)); authorizedClients.put(authorizedClient.getClientRegistration().getRegistrationId(), authorizedClient); request.getSession().setAttribute(AUTHORIZED_CLIENTS_ATTR_NAME, - serializeOAuth2AuthorizedClientMap(authorizedClients)); + serializeOAuth2AuthorizedClientMap(authorizedClients)); } @Override @@ -59,13 +61,13 @@ public void removeAuthorizedClient(String clientRegistrationId, Authentication p HttpServletRequest request, HttpServletResponse response) { Assert.hasText(clientRegistrationId, "clientRegistrationId cannot be empty"); Assert.notNull(request, MSG_REQUEST_CANNOT_BE_NULL); - Map authorizedClients = this.getAuthorizedClients(request); + Map authorizedClients = new HashMap<>(this.getAuthorizedClients(request)); if (authorizedClients.remove(clientRegistrationId) != null) { if (authorizedClients.isEmpty()) { request.getSession().removeAttribute(AUTHORIZED_CLIENTS_ATTR_NAME); } else { request.getSession().setAttribute(AUTHORIZED_CLIENTS_ATTR_NAME, - serializeOAuth2AuthorizedClientMap(authorizedClients)); + serializeOAuth2AuthorizedClientMap(authorizedClients)); } } @@ -74,9 +76,9 @@ public void removeAuthorizedClient(String clientRegistrationId, Authentication p private Map getAuthorizedClients(HttpServletRequest request) { HttpSession session = request.getSession(false); return Optional.ofNullable(session) - .map(s -> s.getAttribute(AUTHORIZED_CLIENTS_ATTR_NAME)) - .map(Object::toString) - .map(SerializerUtils::deserializeOAuth2AuthorizedClientMap) - .orElse(Collections.emptyMap()); + .map(s -> s.getAttribute(AUTHORIZED_CLIENTS_ATTR_NAME)) + .map(Object::toString) + .map(SerializerUtils::deserializeOAuth2AuthorizedClientMap) + .orElse(Collections.emptyMap()); } } diff --git a/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/aad/implementation/oauth2/JacksonHttpSessionOAuth2AuthorizedClientRepositoryTest.java b/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/aad/implementation/oauth2/JacksonHttpSessionOAuth2AuthorizedClientRepositoryTest.java new file mode 100644 index 000000000000..01b51112160d --- /dev/null +++ b/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/aad/implementation/oauth2/JacksonHttpSessionOAuth2AuthorizedClientRepositoryTest.java @@ -0,0 +1,240 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.spring.cloud.autoconfigure.aad.implementation.oauth2; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.security.oauth2.client.OAuth2AuthorizedClient; +import org.springframework.security.oauth2.client.registration.ClientRegistration; +import org.springframework.security.oauth2.core.AuthorizationGrantType; +import org.springframework.security.oauth2.core.ClientAuthenticationMethod; +import org.springframework.security.oauth2.core.OAuth2AccessToken; + +import javax.servlet.http.HttpSession; +import java.time.Instant; +import java.util.Map; + +import static com.azure.spring.cloud.autoconfigure.aad.implementation.jackson.SerializerUtils.deserializeOAuth2AuthorizedClientMap; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.springframework.security.oauth2.core.OAuth2AccessToken.TokenType.BEARER; + +class JacksonHttpSessionOAuth2AuthorizedClientRepositoryTest { + private final String principalName1 = "principalName-1"; + private final String principalName2 = "principalName-2"; + + private final ClientRegistration registration1 = ClientRegistration + .withRegistrationId("registration-id-1") + .redirectUri("{baseUrl}/{action}/oauth2/code/{registrationId}") + .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC) + .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE) + .scope("scope-1") + .authorizationUri("https://example1.com/login/oauth/authorize") + .tokenUri("https://example1.com/login/oauth/access_token") + .jwkSetUri("https://example1.com/oauth2/jwk") + .issuerUri("https://example1.com") + .userInfoUri("https://api.example1.com/user") + .userNameAttributeName("id-1") + .clientName("Client Name 1") + .clientId("client-id-1") + .clientSecret("client-secret-1") + .build(); + + private final ClientRegistration registration2 = ClientRegistration + .withRegistrationId("registration-id-2") + .redirectUri("{baseUrl}/{action}/oauth2/code/{registrationId}") + .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC) + .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE) + .scope("scope-2") + .authorizationUri("https://example2.com/login/oauth/authorize") + .tokenUri("https://example2.com/login/oauth/access_token") + .userInfoUri("https://api.example2.com/user") + .userNameAttributeName("id-2") + .clientName("Client Name 2") + .clientId("client-id-2") + .clientSecret("client-secret-2") + .build(); + + private final String registrationId1 = this.registration1.getRegistrationId(); + + private final String registrationId2 = this.registration2.getRegistrationId(); + + private final OAuth2AccessToken oAuth2AccessToken1 = new OAuth2AccessToken(BEARER, "tokenValue1", Instant.now(), Instant.now().plusMillis(3_600_000)); + private final OAuth2AccessToken oAuth2AccessToken2 = new OAuth2AccessToken(BEARER, "tokenValue2", Instant.now(), Instant.now().plusMillis(3_600_000)); + + private final OAuth2AuthorizedClient authorizedClient1 = new OAuth2AuthorizedClient(this.registration1, this.principalName1, oAuth2AccessToken1); + + private final OAuth2AuthorizedClient authorizedClient2 = new OAuth2AuthorizedClient(this.registration2, this.principalName2, oAuth2AccessToken2); + + private final JacksonHttpSessionOAuth2AuthorizedClientRepository authorizedClientRepository = + new JacksonHttpSessionOAuth2AuthorizedClientRepository(); + private MockHttpServletRequest request; + + private MockHttpServletResponse response; + + @BeforeEach + void setup() { + this.request = new MockHttpServletRequest(); + this.response = new MockHttpServletResponse(); + } + + @Test + void loadAuthorizedClientWhenClientRegistrationIdIsNullThenThrowIllegalArgumentException() { + assertThatIllegalArgumentException().isThrownBy(() -> + this.authorizedClientRepository.loadAuthorizedClient(null, null, this.request)); + } + + @Test + void loadAuthorizedClientWhenPrincipalNameIsNullThenExceptionNotThrown() { + this.authorizedClientRepository.loadAuthorizedClient(this.registrationId1, null, this.request); + } + + @Test + void loadAuthorizedClientWhenRequestIsNullThenThrowIllegalArgumentException() { + assertThatIllegalArgumentException().isThrownBy(() -> + this.authorizedClientRepository.loadAuthorizedClient(this.registrationId1, null, null)); + } + + @Test + void loadAuthorizedClientWhenClientRegistrationNotFoundThenReturnNull() { + OAuth2AuthorizedClient authorizedClient = + this.authorizedClientRepository.loadAuthorizedClient("registration-not-found", null, this.request); + assertThat(authorizedClient).isNull(); + } + + @Test + void loadAuthorizedClientWhenSavedThenReturnAuthorizedClient() { + this.authorizedClientRepository.saveAuthorizedClient(authorizedClient1, null, this.request, this.response); + OAuth2AuthorizedClient loadedAuthorizedClient = + this.authorizedClientRepository.loadAuthorizedClient(this.registrationId1, null, this.request); + assertSame(authorizedClient1, loadedAuthorizedClient); + } + + @Test + void saveAuthorizedClientWhenAuthorizedClientIsNullThenThrowIllegalArgumentException() { + assertThatIllegalArgumentException().isThrownBy(() -> + this.authorizedClientRepository.saveAuthorizedClient(null, null, this.request, this.response)); + } + + @Test + void saveAuthorizedClientWhenAuthenticationIsNullThenExceptionNotThrown() { + this.authorizedClientRepository.saveAuthorizedClient(authorizedClient1, null, this.request, this.response); + } + + @Test + void saveAuthorizedClientWhenRequestIsNullThenThrowIllegalArgumentException() { + assertThatIllegalArgumentException().isThrownBy(() -> + this.authorizedClientRepository.saveAuthorizedClient(authorizedClient1, null, null, this.response)); + } + + @Test + void saveAuthorizedClientWhenResponseIsNullThenThrowIllegalArgumentException() { + assertThatIllegalArgumentException().isThrownBy(() -> + this.authorizedClientRepository.saveAuthorizedClient(authorizedClient1, null, this.request, null)); + } + + @Test + void saveAuthorizedClientWhenSavedThenSavedToSession() { + this.authorizedClientRepository.saveAuthorizedClient(authorizedClient1, null, this.request, this.response); + + HttpSession session = this.request.getSession(false); + assertThat(session).isNotNull(); + String authorizedClientsString = (String) session.getAttribute( + JacksonHttpSessionOAuth2AuthorizedClientRepository.class.getName() + ".AUTHORIZED_CLIENTS"); + Map authorizedClients = deserializeOAuth2AuthorizedClientMap(authorizedClientsString); + assertThat(authorizedClients).isNotEmpty(); + assertThat(authorizedClients).hasSize(1); + OAuth2AuthorizedClient loadedAuthorizedClient = authorizedClients.values().iterator().next(); + assertSame(authorizedClient1, loadedAuthorizedClient); + } + + @Test + void removeAuthorizedClientWhenClientRegistrationIdIsNullThenThrowIllegalArgumentException() { + assertThatIllegalArgumentException().isThrownBy(() -> + this.authorizedClientRepository.removeAuthorizedClient(null, null, this.request, this.response)); + } + + @Test + void removeAuthorizedClientWhenPrincipalNameIsNullThenExceptionNotThrown() { + this.authorizedClientRepository.removeAuthorizedClient(this.registrationId1, null, this.request, this.response); + } + + @Test + void removeAuthorizedClientWhenRequestIsNullThenThrowIllegalArgumentException() { + assertThatIllegalArgumentException().isThrownBy(() -> + this.authorizedClientRepository.removeAuthorizedClient(this.registrationId1, null, null, this.response)); + } + + @Test + void removeAuthorizedClientWhenResponseIsNullThenExceptionNotThrown() { + this.authorizedClientRepository.removeAuthorizedClient(this.registrationId1, null, this.request, null); + } + + @Test + void removeAuthorizedClientWhenNotSavedThenSessionNotCreated() { + this.authorizedClientRepository.removeAuthorizedClient(this.registrationId2, null, this.request, this.response); + assertThat(this.request.getSession(false)).isNull(); + } + + @Test + void removeAuthorizedClientWhenClient1SavedAndClient2RemovedThenClient1NotRemoved() { + this.authorizedClientRepository.saveAuthorizedClient(authorizedClient1, null, this.request, this.response); + // Remove registrationId2 (never added so is not removed either) + this.authorizedClientRepository.removeAuthorizedClient(this.registrationId2, null, this.request, this.response); + OAuth2AuthorizedClient loadedAuthorizedClient1 = + this.authorizedClientRepository.loadAuthorizedClient(this.registrationId1, null, this.request); + assertThat(loadedAuthorizedClient1).isNotNull(); + assertSame(authorizedClient1, loadedAuthorizedClient1); + } + + @Test + void removeAuthorizedClientWhenSavedThenRemoved() { + this.authorizedClientRepository.saveAuthorizedClient(authorizedClient1, null, this.request, this.response); + OAuth2AuthorizedClient loadedAuthorizedClient = + this.authorizedClientRepository.loadAuthorizedClient(this.registrationId1, null, this.request); + assertSame(authorizedClient1, loadedAuthorizedClient); + this.authorizedClientRepository.removeAuthorizedClient(this.registrationId1, null, this.request, this.response); + loadedAuthorizedClient = this.authorizedClientRepository.loadAuthorizedClient(this.registrationId1, null, this.request); + assertThat(loadedAuthorizedClient).isNull(); + } + + @Test + void removeAuthorizedClientWhenSavedThenRemovedFromSession() { + this.authorizedClientRepository.saveAuthorizedClient(authorizedClient1, null, this.request, this.response); + OAuth2AuthorizedClient loadedAuthorizedClient = + this.authorizedClientRepository.loadAuthorizedClient(this.registrationId1, null, this.request); + assertSame(authorizedClient1, loadedAuthorizedClient); + this.authorizedClientRepository.removeAuthorizedClient(this.registrationId1, null, this.request, this.response); + HttpSession session = this.request.getSession(false); + assertThat(session).isNotNull(); + assertThat(session.getAttribute(JacksonHttpSessionOAuth2AuthorizedClientRepository.class.getName() + ".AUTHORIZED_CLIENTS")).isNull(); + } + + @Test + void removeAuthorizedClientWhenClient1Client2SavedAndClient1RemovedThenClient2NotRemoved() { + this.authorizedClientRepository.saveAuthorizedClient(authorizedClient1, null, this.request, this.response); + this.authorizedClientRepository.saveAuthorizedClient(authorizedClient2, null, this.request, this.response); + this.authorizedClientRepository.removeAuthorizedClient(this.registrationId1, null, this.request, this.response); + OAuth2AuthorizedClient loadedAuthorizedClient2 = + this.authorizedClientRepository.loadAuthorizedClient(this.registrationId2, null, this.request); + assertThat(loadedAuthorizedClient2).isNotNull(); + assertSame(authorizedClient2, loadedAuthorizedClient2); + } + + private void assertSame(OAuth2AuthorizedClient client1, OAuth2AuthorizedClient client2) { + assertEquals(client1.getClientRegistration().getClientId(), client2.getClientRegistration().getClientId()); + assertEquals(client1.getClientRegistration().getRegistrationId(), client2.getClientRegistration().getRegistrationId()); + assertEquals(client1.getClientRegistration().getClientName(), client2.getClientRegistration().getClientName()); + assertEquals(client1.getClientRegistration().getClientSecret(), client2.getClientRegistration().getClientSecret()); + assertEquals(client1.getClientRegistration().getClientAuthenticationMethod(), client2.getClientRegistration().getClientAuthenticationMethod()); + assertEquals(client1.getClientRegistration().getAuthorizationGrantType(), client2.getClientRegistration().getAuthorizationGrantType()); + assertEquals(client1.getPrincipalName(), client2.getPrincipalName()); + assertEquals(client1.getAccessToken().getTokenType(), client2.getAccessToken().getTokenType()); + assertEquals(client1.getAccessToken().getTokenValue(), client2.getAccessToken().getTokenValue()); + assertEquals(client1.getAccessToken().getScopes(), client2.getAccessToken().getScopes()); + } +} From 41f9949b13c44c12f480bab12f1e2dcc6e69beb9 Mon Sep 17 00:00:00 2001 From: Rujun Chen Date: Fri, 21 Oct 2022 17:02:41 +0800 Subject: [PATCH 2/8] Fix #31191. duplicated "scope" parameter. --- ...zationCodeGrantRequestEntityConverter.java | 31 ++++---- ...nCodeGrantRequestEntityConverterTests.java | 76 +++++++++++-------- 2 files changed, 58 insertions(+), 49 deletions(-) diff --git a/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/aad/implementation/oauth2/AbstractOAuth2AuthorizationCodeGrantRequestEntityConverter.java b/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/aad/implementation/oauth2/AbstractOAuth2AuthorizationCodeGrantRequestEntityConverter.java index af91381bb842..4318462b709c 100644 --- a/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/aad/implementation/oauth2/AbstractOAuth2AuthorizationCodeGrantRequestEntityConverter.java +++ b/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/aad/implementation/oauth2/AbstractOAuth2AuthorizationCodeGrantRequestEntityConverter.java @@ -4,12 +4,11 @@ package com.azure.spring.cloud.autoconfigure.aad.implementation.oauth2; import com.azure.spring.cloud.core.implementation.util.AzureSpringIdentifier; -import org.springframework.core.convert.converter.Converter; import org.springframework.http.HttpHeaders; -import org.springframework.http.RequestEntity; import org.springframework.security.oauth2.client.endpoint.OAuth2AuthorizationCodeGrantRequest; import org.springframework.security.oauth2.client.endpoint.OAuth2AuthorizationCodeGrantRequestEntityConverter; import org.springframework.util.MultiValueMap; +import org.springframework.util.MultiValueMapAdapter; import java.util.Collections; import java.util.UUID; @@ -18,7 +17,15 @@ * When using "auth-code" in AAD and AAD B2C, it's used to expand head and body parameters of the request. */ public abstract class AbstractOAuth2AuthorizationCodeGrantRequestEntityConverter - extends OAuth2AuthorizationCodeGrantRequestEntityConverter { + extends OAuth2AuthorizationCodeGrantRequestEntityConverter { + + private static final MultiValueMap EMPTY_MULTI_VALUE_MAP = + new MultiValueMapAdapter<>(Collections.emptyMap()); + + protected AbstractOAuth2AuthorizationCodeGrantRequestEntityConverter() { + addHeadersConverter(this::getHttpHeaders); + addParametersConverter(this::getHttpBody); + } /** * Gets the application ID. @@ -27,23 +34,11 @@ public abstract class AbstractOAuth2AuthorizationCodeGrantRequestEntityConverter */ protected abstract String getApplicationId(); - @Override - @SuppressWarnings("unchecked") - public RequestEntity convert(OAuth2AuthorizationCodeGrantRequest request) { - addHeadersConverter(headersConverter); - addParametersConverter(parametersConverter); - return super.convert(request); - } - - private final Converter headersConverter = (request) -> getHttpHeaders(); - - private final Converter> parametersConverter = this::getHttpBody; - /** * Additional default headers information. * @return HttpHeaders */ - public HttpHeaders getHttpHeaders() { + protected HttpHeaders getHttpHeaders(OAuth2AuthorizationCodeGrantRequest request) { HttpHeaders httpHeaders = new HttpHeaders(); httpHeaders.put("x-client-SKU", Collections.singletonList(getApplicationId())); httpHeaders.put("x-client-VER", Collections.singletonList(AzureSpringIdentifier.VERSION)); @@ -56,7 +51,7 @@ public HttpHeaders getHttpHeaders() { * @param request OAuth2AuthorizationCodeGrantRequest * @return MultiValueMap */ - public MultiValueMap getHttpBody(OAuth2AuthorizationCodeGrantRequest request) { - return null; + protected MultiValueMap getHttpBody(OAuth2AuthorizationCodeGrantRequest request) { + return EMPTY_MULTI_VALUE_MAP; } } diff --git a/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/aad/implementation/webapp/AadOAuth2AuthorizationCodeGrantRequestEntityConverterTests.java b/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/aad/implementation/webapp/AadOAuth2AuthorizationCodeGrantRequestEntityConverterTests.java index 1e110859858b..3e89a8aa8ad2 100644 --- a/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/aad/implementation/webapp/AadOAuth2AuthorizationCodeGrantRequestEntityConverterTests.java +++ b/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/aad/implementation/webapp/AadOAuth2AuthorizationCodeGrantRequestEntityConverterTests.java @@ -5,7 +5,7 @@ import com.azure.spring.cloud.autoconfigure.aad.AadClientRegistrationRepository; import com.azure.spring.cloud.autoconfigure.aad.implementation.WebApplicationContextRunnerUtils; -import org.hamcrest.Matcher; +import com.azure.spring.cloud.core.implementation.util.AzureSpringIdentifier; import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.runner.WebApplicationContextRunner; import org.springframework.http.HttpEntity; @@ -19,31 +19,31 @@ import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponse; import org.springframework.util.MultiValueMap; +import java.util.Collections; import java.util.Optional; import static com.azure.spring.cloud.autoconfigure.aad.AadClientRegistrationRepository.AZURE_CLIENT_REGISTRATION_ID; -import static org.hamcrest.CoreMatchers.hasItems; -import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; class AadOAuth2AuthorizationCodeGrantRequestEntityConverterTests { private WebApplicationContextRunner getContextRunner() { return WebApplicationContextRunnerUtils - .webApplicationContextRunner() - .withPropertyValues( - "spring.cloud.azure.active-directory.enabled = true", - "spring.cloud.azure.active-directory.base-uri = fake-uri", - "spring.cloud.azure.active-directory.authorization-clients.graph.scopes = Graph.Scope", - "spring.cloud.azure.active-directory.authorization-clients.arm.scopes = Arm.Scope", - "spring.cloud.azure.active-directory.authorization-clients.arm.authorization-grant-type = authorization_code"); + .webApplicationContextRunner() + .withPropertyValues( + "spring.cloud.azure.active-directory.enabled = true", + "spring.cloud.azure.active-directory.base-uri = fake-uri", + "spring.cloud.azure.active-directory.authorization-clients.graph.scopes = Graph.Scope", + "spring.cloud.azure.active-directory.authorization-clients.arm.scopes = Arm.Scope", + "spring.cloud.azure.active-directory.authorization-clients.arm.authorization-grant-type = authorization_code"); } @Test void addScopeForAzureClient() { getContextRunner().run(context -> { AadClientRegistrationRepository repository = - (AadClientRegistrationRepository) context.getBean(ClientRegistrationRepository.class); + (AadClientRegistrationRepository) context.getBean(ClientRegistrationRepository.class); ClientRegistration azure = repository.findByRegistrationId(AZURE_CLIENT_REGISTRATION_ID); MultiValueMap body = convertedBodyOf(repository, createCodeGrantRequest(azure)); assertEquals("openid profile offline_access", body.getFirst("scope")); @@ -54,7 +54,7 @@ void addScopeForAzureClient() { void addScopeForAuthorizationCodeClient() { getContextRunner().run(context -> { AadClientRegistrationRepository repository = - (AadClientRegistrationRepository) context.getBean(ClientRegistrationRepository.class); + (AadClientRegistrationRepository) context.getBean(ClientRegistrationRepository.class); ClientRegistration arm = repository.findByRegistrationId("arm"); MultiValueMap body = convertedBodyOf(repository, createCodeGrantRequest(arm)); assertEquals("Arm.Scope openid profile offline_access", body.getFirst("scope")); @@ -62,52 +62,66 @@ void addScopeForAuthorizationCodeClient() { } @Test - @SuppressWarnings("unchecked") + void onlyAddScopeOnceEvenConvertMethodExecutedMultipleTimes() { + getContextRunner().run(context -> { + AadClientRegistrationRepository repository = + (AadClientRegistrationRepository) context.getBean(ClientRegistrationRepository.class); + AadOAuth2AuthorizationCodeGrantRequestEntityConverter converter = + new AadOAuth2AuthorizationCodeGrantRequestEntityConverter(repository.getAzureClientAccessTokenScopes()); + ClientRegistration azure = repository.findByRegistrationId(AZURE_CLIENT_REGISTRATION_ID); + OAuth2AuthorizationCodeGrantRequest request = createCodeGrantRequest(azure); + // Convert method execute 2 times + converter.convert(request); + RequestEntity entity = converter.convert(request); + MultiValueMap map = WebApplicationContextRunnerUtils.toMultiValueMap(entity); + assertEquals(1, map.get("scope").size()); + }); + } + + @Test void addHeadersForAzureClient() { getContextRunner().run(context -> { AadClientRegistrationRepository repository = - (AadClientRegistrationRepository) context.getBean(ClientRegistrationRepository.class); + (AadClientRegistrationRepository) context.getBean(ClientRegistrationRepository.class); ClientRegistration azure = repository.findByRegistrationId(AZURE_CLIENT_REGISTRATION_ID); HttpHeaders httpHeaders = convertedHeaderOf(repository, createCodeGrantRequest(azure)); - assertThat(httpHeaders.entrySet(), (Matcher) hasItems(expectedHeaders(repository))); + testHttpHeaders(httpHeaders); }); } @Test - @SuppressWarnings("unchecked") void addHeadersForAuthorizationCodeClient() { getContextRunner().run(context -> { AadClientRegistrationRepository repository = - (AadClientRegistrationRepository) context.getBean(ClientRegistrationRepository.class); + (AadClientRegistrationRepository) context.getBean(ClientRegistrationRepository.class); ClientRegistration arm = repository.findByRegistrationId("arm"); HttpHeaders httpHeaders = convertedHeaderOf(repository, createCodeGrantRequest(arm)); - assertThat(httpHeaders.entrySet(), (Matcher) hasItems(expectedHeaders(repository))); + testHttpHeaders(httpHeaders); }); } private HttpHeaders convertedHeaderOf(AadClientRegistrationRepository repository, OAuth2AuthorizationCodeGrantRequest request) { AadOAuth2AuthorizationCodeGrantRequestEntityConverter converter = - new AadOAuth2AuthorizationCodeGrantRequestEntityConverter(repository.getAzureClientAccessTokenScopes()); + new AadOAuth2AuthorizationCodeGrantRequestEntityConverter(repository.getAzureClientAccessTokenScopes()); RequestEntity entity = converter.convert(request); return Optional.ofNullable(entity) - .map(HttpEntity::getHeaders) - .orElse(null); + .map(HttpEntity::getHeaders) + .orElse(null); } - private Object[] expectedHeaders(AadClientRegistrationRepository repository) { - return new AadOAuth2AuthorizationCodeGrantRequestEntityConverter(repository.getAzureClientAccessTokenScopes()) - .getHttpHeaders() - .entrySet() - .stream() - .filter(entry -> !entry.getKey().equals("client-request-id")) - .toArray(); + private void testHttpHeaders(HttpHeaders headers) { + assertTrue(headers.containsKey("x-client-SKU")); + assertEquals(Collections.singletonList(AzureSpringIdentifier.AZURE_SPRING_AAD), headers.get("x-client-SKU")); + assertTrue(headers.containsKey("x-client-VER")); + assertEquals(Collections.singletonList(AzureSpringIdentifier.VERSION), headers.get("x-client-VER")); + assertTrue(headers.containsKey("client-request-id")); } private MultiValueMap convertedBodyOf(AadClientRegistrationRepository repository, OAuth2AuthorizationCodeGrantRequest request) { AadOAuth2AuthorizationCodeGrantRequestEntityConverter converter = - new AadOAuth2AuthorizationCodeGrantRequestEntityConverter(repository.getAzureClientAccessTokenScopes()); + new AadOAuth2AuthorizationCodeGrantRequestEntityConverter(repository.getAzureClientAccessTokenScopes()); RequestEntity entity = converter.convert(request); return WebApplicationContextRunnerUtils.toMultiValueMap(entity); } @@ -118,8 +132,8 @@ private OAuth2AuthorizationCodeGrantRequest createCodeGrantRequest(ClientRegistr private OAuth2AuthorizationExchange createExchange(ClientRegistration client) { return new OAuth2AuthorizationExchange( - createAuthorizationRequest(client), - createAuthorizationResponse()); + createAuthorizationRequest(client), + createAuthorizationResponse()); } private OAuth2AuthorizationRequest createAuthorizationRequest(ClientRegistration client) { From f953d294d3de1e4cc6f80732d3813b5fc876368a Mon Sep 17 00:00:00 2001 From: Rujun Chen Date: Fri, 21 Oct 2022 17:13:24 +0800 Subject: [PATCH 3/8] Fix 31218 rest template is not well configured when jwk resolver is null --- .../AadOAuth2ClientConfiguration.java | 93 +++--- ...tTemplateProxyCustomizerConfiguration.java | 31 ++ ...bstractAzureServiceConfigurationTests.java | 88 +++--- .../AadRestTemplateCreatorTest.java | 120 ++++---- .../AadOAuth2ClientConfigurationTests.java | 270 ++++++++++-------- .../useragent/CosmosUserAgentTests.java | 36 +-- .../amqp/EventHubsUserAgentTests.java | 6 +- .../amqp/EventProcessorUserAgentTests.java | 6 +- .../amqp/ServiceBusUserAgentTests.java | 4 +- .../ServiceBusProcessorUserAgentTests.java | 6 +- .../ServiceBusReceiverUserAgentTests.java | 6 +- .../ServiceBusSenderUserAgentTests.java | 6 +- ...viceBusSessionProcessorUserAgentTests.java | 6 +- ...rviceBusSessionReceiverUserAgentTests.java | 6 +- .../useragent/util/UserAgentTestUtil.java | 14 +- 15 files changed, 391 insertions(+), 307 deletions(-) create mode 100644 sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/aad/implementation/RestTemplateProxyCustomizerConfiguration.java diff --git a/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/aad/configuration/AadOAuth2ClientConfiguration.java b/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/aad/configuration/AadOAuth2ClientConfiguration.java index a6d8369d2253..a75a0953a428 100644 --- a/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/aad/configuration/AadOAuth2ClientConfiguration.java +++ b/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/aad/configuration/AadOAuth2ClientConfiguration.java @@ -12,7 +12,10 @@ import com.azure.spring.cloud.autoconfigure.aad.implementation.oauth2.OAuth2ClientAuthenticationJwkResolver; import com.azure.spring.cloud.autoconfigure.aad.implementation.webapi.AadJwtBearerGrantRequestEntityConverter; import com.azure.spring.cloud.autoconfigure.aad.implementation.webapp.AadAzureDelegatedOAuth2AuthorizedClientProvider; +import com.azure.spring.cloud.autoconfigure.aad.implementation.webapp.AadOidcIdTokenDecoderFactory; import com.azure.spring.cloud.autoconfigure.aad.properties.AadAuthenticationProperties; +import com.azure.spring.cloud.autoconfigure.aad.properties.AadAuthorizationServerEndpoints; +import com.azure.spring.cloud.autoconfigure.aad.properties.AadProfileProperties; import com.nimbusds.jose.jwk.JWK; import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; @@ -20,6 +23,7 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.web.configurers.oauth2.client.OAuth2LoginConfigurer; import org.springframework.security.oauth2.client.JwtBearerOAuth2AuthorizedClientProvider; import org.springframework.security.oauth2.client.OAuth2AuthorizedClientManager; import org.springframework.security.oauth2.client.OAuth2AuthorizedClientProvider; @@ -36,8 +40,12 @@ import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; import org.springframework.security.oauth2.client.web.DefaultOAuth2AuthorizedClientManager; import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository; +import org.springframework.security.oauth2.jwt.JwtDecoder; +import org.springframework.security.oauth2.jwt.JwtDecoderFactory; +import org.springframework.web.client.RestTemplate; import static com.azure.spring.cloud.autoconfigure.aad.implementation.AadRestTemplateCreator.createOAuth2AccessTokenResponseClientRestTemplate; +import static com.azure.spring.cloud.autoconfigure.aad.implementation.AadRestTemplateCreator.createRestTemplate; /** *

@@ -93,8 +101,8 @@ public OAuth2AuthorizedClientRepository oAuth2AuthorizedClientRepository() { @Conditional(ClientCertificatePropertiesCondition.class) OAuth2ClientAuthenticationJwkResolver oAuth2ClientAuthenticationJwkResolver(AadAuthenticationProperties properties) { return new AadOAuth2ClientAuthenticationJwkResolver( - properties.getCredential().getClientCertificatePath(), - properties.getCredential().getClientCertificatePassword()); + properties.getCredential().getClientCertificatePath(), + properties.getCredential().getClientCertificatePassword()); } /** @@ -116,21 +124,21 @@ OAuth2AuthorizedClientManager authorizedClientManager( JwtBearerOAuth2AuthorizedClientProvider jwtBearerProvider, ObjectProvider jwkResolvers) { DefaultOAuth2AuthorizedClientManager manager = - new DefaultOAuth2AuthorizedClientManager(clientRegistrations, authorizedClients); + new DefaultOAuth2AuthorizedClientManager(clientRegistrations, authorizedClients); OAuth2ClientAuthenticationJwkResolver jwkResolver = jwkResolvers.getIfUnique(); // @formatter:off OAuth2AuthorizedClientProvider providers = - OAuth2AuthorizedClientProviderBuilder - .builder() - .authorizationCode() - .clientCredentials(builder -> - clientCredentialsGrantBuilderAccessTokenResponseClientCustomizer(builder, jwkResolver)) - .password(builder -> passwordGrantBuilderAccessTokenResponseClientCustomizer(builder, jwkResolver)) - .provider(refreshTokenProvider) - .provider(jwtBearerProvider) - .provider(azureDelegatedOAuth2AuthorizedClientProvider(refreshTokenProvider, authorizedClients)) - .build(); + OAuth2AuthorizedClientProviderBuilder + .builder() + .authorizationCode() + .clientCredentials(builder -> + clientCredentialsGrantBuilderAccessTokenResponseClientCustomizer(builder, jwkResolver)) + .password(builder -> passwordGrantBuilderAccessTokenResponseClientCustomizer(builder, jwkResolver)) + .provider(refreshTokenProvider) + .provider(jwtBearerProvider) + .provider(azureDelegatedOAuth2AuthorizedClientProvider(refreshTokenProvider, authorizedClients)) + .build(); // @formatter:on manager.setAuthorizedClientProvider(providers); return manager; @@ -142,14 +150,14 @@ JwtBearerOAuth2AuthorizedClientProvider azureAdJwtBearerProvider( ObjectProvider resolvers) { JwtBearerOAuth2AuthorizedClientProvider provider = new JwtBearerOAuth2AuthorizedClientProvider(); OAuth2ClientAuthenticationJwkResolver resolver = resolvers.getIfUnique(); - AadJwtBearerGrantRequestEntityConverter jwtBearerConverter = new AadJwtBearerGrantRequestEntityConverter(); + DefaultJwtBearerTokenResponseClient client = new DefaultJwtBearerTokenResponseClient(); + client.setRestOperations(createOAuth2AccessTokenResponseClientRestTemplate(restTemplateBuilder)); + AadJwtBearerGrantRequestEntityConverter converter = new AadJwtBearerGrantRequestEntityConverter(); if (resolver != null) { - jwtBearerConverter.addParametersConverter(new AadJwtClientAuthenticationParametersConverter<>(resolver::resolve)); + converter.addParametersConverter(new AadJwtClientAuthenticationParametersConverter<>(resolver::resolve)); } - DefaultJwtBearerTokenResponseClient responseClient = new DefaultJwtBearerTokenResponseClient(); - responseClient.setRestOperations(createOAuth2AccessTokenResponseClientRestTemplate(restTemplateBuilder)); - responseClient.setRequestEntityConverter(jwtBearerConverter); - provider.setAccessTokenResponseClient(responseClient); + client.setRequestEntityConverter(converter); + provider.setAccessTokenResponseClient(client); return provider; } @@ -159,52 +167,63 @@ RefreshTokenOAuth2AuthorizedClientProvider azureRefreshTokenProvider( ObjectProvider resolvers) { RefreshTokenOAuth2AuthorizedClientProvider provider = new RefreshTokenOAuth2AuthorizedClientProvider(); OAuth2ClientAuthenticationJwkResolver resolver = resolvers.getIfUnique(); + DefaultRefreshTokenTokenResponseClient client = new DefaultRefreshTokenTokenResponseClient(); + client.setRestOperations(createOAuth2AccessTokenResponseClientRestTemplate(restTemplateBuilder)); if (resolver != null) { OAuth2RefreshTokenGrantRequestEntityConverter converter = new OAuth2RefreshTokenGrantRequestEntityConverter(); converter.addParametersConverter(new AadJwtClientAuthenticationParametersConverter<>(resolver::resolve)); - - DefaultRefreshTokenTokenResponseClient responseClient = new DefaultRefreshTokenTokenResponseClient(); - responseClient.setRestOperations(createOAuth2AccessTokenResponseClientRestTemplate(restTemplateBuilder)); - responseClient.setRequestEntityConverter(converter); - provider.setAccessTokenResponseClient(responseClient); + client.setRequestEntityConverter(converter); } + provider.setAccessTokenResponseClient(client); return provider; } + /** + * Provide {@link JwtDecoderFactory} used in {@link OAuth2LoginConfigurer#init}. The {@link JwtDecoder} created by + * current {@link JwtDecoderFactory} will use {@link RestTemplate} created by {@link RestTemplateBuilder} bean. + * + * @param properties the AadAuthenticationProperties + * @return JwtDecoderFactory + */ + @Bean + @ConditionalOnMissingBean + JwtDecoderFactory azureAdJwtDecoderFactory(AadAuthenticationProperties properties) { + AadProfileProperties profile = properties.getProfile(); + AadAuthorizationServerEndpoints endpoints = new AadAuthorizationServerEndpoints( + profile.getEnvironment().getActiveDirectoryEndpoint(), profile.getTenantId()); + return new AadOidcIdTokenDecoderFactory(endpoints.getJwkSetEndpoint(), createRestTemplate(restTemplateBuilder)); + } + private void passwordGrantBuilderAccessTokenResponseClientCustomizer( OAuth2AuthorizedClientProviderBuilder.PasswordGrantBuilder builder, OAuth2ClientAuthenticationJwkResolver resolver) { + DefaultPasswordTokenResponseClient client = new DefaultPasswordTokenResponseClient(); + client.setRestOperations(createOAuth2AccessTokenResponseClientRestTemplate(restTemplateBuilder)); if (resolver != null) { OAuth2PasswordGrantRequestEntityConverter converter = new OAuth2PasswordGrantRequestEntityConverter(); converter.addParametersConverter(new AadJwtClientAuthenticationParametersConverter<>(resolver::resolve)); - - DefaultPasswordTokenResponseClient client = new DefaultPasswordTokenResponseClient(); - client.setRestOperations(createOAuth2AccessTokenResponseClientRestTemplate(restTemplateBuilder)); client.setRequestEntityConverter(converter); - - builder.accessTokenResponseClient(client); } + builder.accessTokenResponseClient(client); } private void clientCredentialsGrantBuilderAccessTokenResponseClientCustomizer( OAuth2AuthorizedClientProviderBuilder.ClientCredentialsGrantBuilder builder, OAuth2ClientAuthenticationJwkResolver resolver) { + DefaultClientCredentialsTokenResponseClient client = new DefaultClientCredentialsTokenResponseClient(); + client.setRestOperations(createOAuth2AccessTokenResponseClientRestTemplate(restTemplateBuilder)); if (resolver != null) { OAuth2ClientCredentialsGrantRequestEntityConverter converter = - new OAuth2ClientCredentialsGrantRequestEntityConverter(); + new OAuth2ClientCredentialsGrantRequestEntityConverter(); converter.addParametersConverter(new AadJwtClientAuthenticationParametersConverter<>(resolver::resolve)); - - DefaultClientCredentialsTokenResponseClient client = new DefaultClientCredentialsTokenResponseClient(); - client.setRestOperations(createOAuth2AccessTokenResponseClientRestTemplate(restTemplateBuilder)); client.setRequestEntityConverter(converter); - - builder.accessTokenResponseClient(client); } + builder.accessTokenResponseClient(client); } private AadAzureDelegatedOAuth2AuthorizedClientProvider azureDelegatedOAuth2AuthorizedClientProvider( - RefreshTokenOAuth2AuthorizedClientProvider refreshTokenProvider, - OAuth2AuthorizedClientRepository authorizedClients) { + RefreshTokenOAuth2AuthorizedClientProvider refreshTokenProvider, + OAuth2AuthorizedClientRepository authorizedClients) { return new AadAzureDelegatedOAuth2AuthorizedClientProvider(refreshTokenProvider, authorizedClients); } } diff --git a/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/aad/implementation/RestTemplateProxyCustomizerConfiguration.java b/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/aad/implementation/RestTemplateProxyCustomizerConfiguration.java new file mode 100644 index 000000000000..2ba71b071fb3 --- /dev/null +++ b/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/aad/implementation/RestTemplateProxyCustomizerConfiguration.java @@ -0,0 +1,31 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.spring.cloud.autoconfigure.aad.implementation; + +import org.springframework.boot.web.client.RestTemplateCustomizer; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.client.SimpleClientHttpRequestFactory; +import org.springframework.web.client.RestTemplate; + +import java.net.InetSocketAddress; +import java.net.Proxy; + +@Configuration +public class RestTemplateProxyCustomizerConfiguration { + + public static final SimpleClientHttpRequestFactory FACTORY = createProxyFactory(); + + @Bean + public RestTemplateCustomizer proxyRestTemplateCustomizer() { + return (RestTemplate restTemplate) -> restTemplate.setRequestFactory(FACTORY); + } + + static SimpleClientHttpRequestFactory createProxyFactory() { + Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress("localhost", 8080)); + SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory(); + factory.setProxy(proxy); + return factory; + } +} diff --git a/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/AbstractAzureServiceConfigurationTests.java b/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/AbstractAzureServiceConfigurationTests.java index cb0f0d422052..15f1fb26d225 100644 --- a/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/AbstractAzureServiceConfigurationTests.java +++ b/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/AbstractAzureServiceConfigurationTests.java @@ -13,7 +13,6 @@ import com.azure.spring.cloud.autoconfigure.context.AzureTokenCredentialAutoConfiguration; import com.azure.spring.cloud.core.implementation.credential.resolver.AzureTokenCredentialResolver; import com.azure.spring.cloud.core.implementation.factory.AbstractAzureServiceClientBuilderFactory; -import com.azure.spring.cloud.core.implementation.util.ReflectionUtils; import com.azure.spring.cloud.core.properties.AzureProperties; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; @@ -21,10 +20,11 @@ import org.springframework.boot.test.context.assertj.AssertableApplicationContext; import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import static com.azure.spring.cloud.core.implementation.util.ReflectionUtils.getField; import static org.assertj.core.api.Assertions.assertThat; public abstract class AbstractAzureServiceConfigurationTests, - P extends AzureProperties> { + P extends AzureProperties> { protected abstract ApplicationContextRunner getMinimalContextRunner(); @@ -37,55 +37,55 @@ public abstract class AbstractAzureServiceConfigurationTests { - assertSovereignCloudsSetInCredential(context, ClientSecretCredential.class); - }); + .withPropertyValues( + getPropertyPrefix() + ".profile.cloud-type=AZURE_US_GOVERNMENT", + getPropertyPrefix() + ".profile.tenant-id=fake-tenant-id", + getPropertyPrefix() + ".credential.client-id=fake-client-id", + getPropertyPrefix() + ".credential.client-secret=fake-client-secret" + ) + .withConfiguration(AutoConfigurations.of( + AzureTokenCredentialAutoConfiguration.class, + AzureGlobalPropertiesAutoConfiguration.class + )) + .run(context -> { + assertSovereignCloudsSetInCredential(context, ClientSecretCredential.class); + }); } @Test protected void usGovCloudShouldWorkWithClientCertificateCredential() { getMinimalContextRunner() - .withPropertyValues( - getPropertyPrefix() + ".profile.cloud-type=AZURE_US_GOVERNMENT", - getPropertyPrefix() + ".profile.tenant-id=fake-tenant-id", - getPropertyPrefix() + ".credential.client-id=fake-client-id", - getPropertyPrefix() + ".credential.client-certificate-path=fake-client-cert-path" - ) - .withConfiguration(AutoConfigurations.of( - AzureTokenCredentialAutoConfiguration.class, - AzureGlobalPropertiesAutoConfiguration.class - )) - .run(context -> { - assertSovereignCloudsSetInCredential(context, ClientCertificateCredential.class); - }); + .withPropertyValues( + getPropertyPrefix() + ".profile.cloud-type=AZURE_US_GOVERNMENT", + getPropertyPrefix() + ".profile.tenant-id=fake-tenant-id", + getPropertyPrefix() + ".credential.client-id=fake-client-id", + getPropertyPrefix() + ".credential.client-certificate-path=fake-client-cert-path" + ) + .withConfiguration(AutoConfigurations.of( + AzureTokenCredentialAutoConfiguration.class, + AzureGlobalPropertiesAutoConfiguration.class + )) + .run(context -> { + assertSovereignCloudsSetInCredential(context, ClientCertificateCredential.class); + }); } @Test protected void usGovCloudShouldWorkWithUsernamePasswordCredential() { getMinimalContextRunner() - .withPropertyValues( - getPropertyPrefix() + ".profile.cloud-type=AZURE_US_GOVERNMENT", - getPropertyPrefix() + ".credential.client-id=fake-client-id", - getPropertyPrefix() + ".credential.username=123", - getPropertyPrefix() + ".credential.password=123" - ) - .withConfiguration(AutoConfigurations.of( - AzureTokenCredentialAutoConfiguration.class, - AzureGlobalPropertiesAutoConfiguration.class - )) - .run(context -> { - assertSovereignCloudsSetInCredential(context, UsernamePasswordCredential.class); - }); + .withPropertyValues( + getPropertyPrefix() + ".profile.cloud-type=AZURE_US_GOVERNMENT", + getPropertyPrefix() + ".credential.client-id=fake-client-id", + getPropertyPrefix() + ".credential.username=123", + getPropertyPrefix() + ".credential.password=123" + ) + .withConfiguration(AutoConfigurations.of( + AzureTokenCredentialAutoConfiguration.class, + AzureGlobalPropertiesAutoConfiguration.class + )) + .run(context -> { + assertSovereignCloudsSetInCredential(context, UsernamePasswordCredential.class); + }); } private void assertSovereignCloudsSetInCredential(AssertableApplicationContext context, Class credentialType) { @@ -105,12 +105,12 @@ private void assertSovereignCloudsSetInCredential(AssertableApplicationConte } private AzureTokenCredentialResolver getAzureTokenCredentialResolver(T builderFactory) { - return (AzureTokenCredentialResolver) ReflectionUtils.getField(getBuilderFactoryType(), - "tokenCredentialResolver", builderFactory); + return (AzureTokenCredentialResolver) getField(getBuilderFactoryType(), + "tokenCredentialResolver", builderFactory); } private IdentityClient getIdentityClient(TokenCredential credential) { - return (IdentityClient) ReflectionUtils.getField(credential.getClass(), "identityClient", credential); + return (IdentityClient) getField(credential.getClass(), "identityClient", credential); } } 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 054a22267433..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,30 +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.boot.web.client.RestTemplateCustomizer; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.http.client.SimpleClientHttpRequestFactory; +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.net.InetSocketAddress; -import java.net.Proxy; +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.AadRestTemplateCreatorTest.RestTemplateProxyCustomizerConfiguration.FACTORY; +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 { @@ -40,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 @@ -76,27 +113,4 @@ void testRestOperationProxyConfiguration() { }); } - static boolean hasItemOfClass(List list, Class clazz) { - return list.stream() - .anyMatch(item -> item.getClass().equals(clazz)); - } - - @Configuration - static class RestTemplateProxyCustomizerConfiguration { - - static final SimpleClientHttpRequestFactory FACTORY = createProxyFactory(); - - @Bean - public RestTemplateCustomizer proxyRestTemplateCustomizer() { - return (RestTemplate restTemplate) -> restTemplate.setRequestFactory(FACTORY); - } - - static SimpleClientHttpRequestFactory createProxyFactory() { - Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress("localhost", 8080)); - SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory(); - factory.setProxy(proxy); - return factory; - } - } - } diff --git a/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/aad/implementation/oauth2/AadOAuth2ClientConfigurationTests.java b/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/aad/implementation/oauth2/AadOAuth2ClientConfigurationTests.java index 823343555450..831847bace3e 100644 --- a/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/aad/implementation/oauth2/AadOAuth2ClientConfigurationTests.java +++ b/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/aad/implementation/oauth2/AadOAuth2ClientConfigurationTests.java @@ -5,6 +5,7 @@ import com.azure.spring.cloud.autoconfigure.aad.AadClientRegistrationRepository; import com.azure.spring.cloud.autoconfigure.aad.configuration.AadOAuth2ClientConfiguration; +import com.azure.spring.cloud.autoconfigure.aad.implementation.RestTemplateProxyCustomizerConfiguration; import com.azure.spring.cloud.autoconfigure.aad.implementation.TestJwks; import com.azure.spring.cloud.autoconfigure.aad.implementation.webapi.AadJwtBearerGrantRequestEntityConverter; import com.azure.spring.cloud.autoconfigure.aad.properties.AadAuthenticationProperties; @@ -33,6 +34,7 @@ import java.util.Arrays; import java.util.Set; +import static com.azure.spring.cloud.autoconfigure.aad.RestTemplateTestUtil.assertRestTemplateWellConfigured; import static com.azure.spring.cloud.autoconfigure.aad.implementation.WebApplicationContextRunnerUtils.oauthClientAndResourceServerRunner; import static com.azure.spring.cloud.autoconfigure.aad.implementation.WebApplicationContextRunnerUtils.resourceServerContextRunner; import static com.azure.spring.cloud.autoconfigure.aad.implementation.WebApplicationContextRunnerUtils.resourceServerWithOboContextRunner; @@ -50,144 +52,144 @@ class AadOAuth2ClientConfigurationTests { @Test void testWithoutAnyPropertiesSet() { new WebApplicationContextRunner() - .withConfiguration(AutoConfigurations.of( - HttpMessageConvertersAutoConfiguration.class, - RestTemplateAutoConfiguration.class)) - .withUserConfiguration(AadOAuth2ClientConfiguration.class) - .run(context -> { - assertThat(context).doesNotHaveBean(AadAuthenticationProperties.class); - assertThat(context).doesNotHaveBean(ClientRegistrationRepository.class); - assertThat(context).doesNotHaveBean(OAuth2AuthorizedClientRepository.class); - }); + .withConfiguration(AutoConfigurations.of( + HttpMessageConvertersAutoConfiguration.class, + RestTemplateAutoConfiguration.class)) + .withUserConfiguration(AadOAuth2ClientConfiguration.class) + .run(context -> { + assertThat(context).doesNotHaveBean(AadAuthenticationProperties.class); + assertThat(context).doesNotHaveBean(ClientRegistrationRepository.class); + assertThat(context).doesNotHaveBean(OAuth2AuthorizedClientRepository.class); + }); } @Test void testWithRequiredPropertiesSet() { oauthClientAndResourceServerRunner() - .withPropertyValues( - "spring.cloud.azure.active-directory.enabled=true", - "spring.cloud.azure.active-directory.credential.client-id=fake-client-id" - ) - .run(context -> { - assertThat(context).hasSingleBean(AadAuthenticationProperties.class); - assertThat(context).hasSingleBean(ClientRegistrationRepository.class); - assertThat(context).hasSingleBean(OAuth2AuthorizedClientRepository.class); - }); + .withPropertyValues( + "spring.cloud.azure.active-directory.enabled=true", + "spring.cloud.azure.active-directory.credential.client-id=fake-client-id" + ) + .run(context -> { + assertThat(context).hasSingleBean(AadAuthenticationProperties.class); + assertThat(context).hasSingleBean(ClientRegistrationRepository.class); + assertThat(context).hasSingleBean(OAuth2AuthorizedClientRepository.class); + }); } @Test void testWebApplication() { webApplicationContextRunner() - .withPropertyValues( - "spring.cloud.azure.active-directory.enabled=true" - ) - .run(context -> { - assertThat(context).hasSingleBean(AadAuthenticationProperties.class); - assertThat(context).hasSingleBean(ClientRegistrationRepository.class); - assertThat(context).hasSingleBean(OAuth2AuthorizedClientRepository.class); - }); + .withPropertyValues( + "spring.cloud.azure.active-directory.enabled=true" + ) + .run(context -> { + assertThat(context).hasSingleBean(AadAuthenticationProperties.class); + assertThat(context).hasSingleBean(ClientRegistrationRepository.class); + assertThat(context).hasSingleBean(OAuth2AuthorizedClientRepository.class); + }); } @Test void testResourceServer() { resourceServerContextRunner() - .run(context -> assertThat(context).doesNotHaveBean(OAuth2AuthorizedClientRepository.class)); + .run(context -> assertThat(context).doesNotHaveBean(OAuth2AuthorizedClientRepository.class)); } @Test void testResourceServerWithOboOnlyGraphClient() { resourceServerWithOboContextRunner() - .withPropertyValues( - "spring.cloud.azure.active-directory.enabled=true", - "spring.cloud.azure.active-directory.authorization-clients.graph.scopes=https://graph.microsoft.com/User.Read") - .run(context -> { - final AadClientRegistrationRepository oboRepo = context.getBean( - AadClientRegistrationRepository.class); - final OAuth2AuthorizedClientRepository aadOboRepo = context.getBean( - OAuth2AuthorizedClientRepository.class); + .withPropertyValues( + "spring.cloud.azure.active-directory.enabled=true", + "spring.cloud.azure.active-directory.authorization-clients.graph.scopes=https://graph.microsoft.com/User.Read") + .run(context -> { + final AadClientRegistrationRepository oboRepo = context.getBean( + AadClientRegistrationRepository.class); + final OAuth2AuthorizedClientRepository aadOboRepo = context.getBean( + OAuth2AuthorizedClientRepository.class); - ClientRegistration graph = oboRepo.findByRegistrationId("graph"); - Set graphScopes = graph.getScopes(); + ClientRegistration graph = oboRepo.findByRegistrationId("graph"); + Set graphScopes = graph.getScopes(); - assertThat(aadOboRepo).isNotNull(); - assertThat(oboRepo).isExactlyInstanceOf(AadClientRegistrationRepository.class); - assertThat(graph).isNotNull(); - assertThat(graphScopes).containsOnly("https://graph.microsoft.com/User.Read"); - }); + assertThat(aadOboRepo).isNotNull(); + assertThat(oboRepo).isExactlyInstanceOf(AadClientRegistrationRepository.class); + assertThat(graph).isNotNull(); + assertThat(graphScopes).containsOnly("https://graph.microsoft.com/User.Read"); + }); } @Test void testResourceServerWithOboInvalidGrantType1() { resourceServerWithOboContextRunner() - .withPropertyValues( - "spring.cloud.azure.active-directory.authorization-clients.graph.authorization-grant-type=authorization_code", - "spring.cloud.azure.active-directory.enabled=true" - ) - .run(context -> - assertThrows(IllegalStateException.class, () -> context.getBean(AadAuthenticationProperties.class)) - ); + .withPropertyValues( + "spring.cloud.azure.active-directory.authorization-clients.graph.authorization-grant-type=authorization_code", + "spring.cloud.azure.active-directory.enabled=true" + ) + .run(context -> + assertThrows(IllegalStateException.class, () -> context.getBean(AadAuthenticationProperties.class)) + ); } @Test void testResourceServerWithOboInvalidGrantType2() { resourceServerWithOboContextRunner() - .withPropertyValues( - "spring.cloud.azure.active-directory.enabled=true", - "spring.cloud.azure.active-directory.authorization-clients.graph.authorization-grant-type=on_behalf_of", - "spring.cloud.azure.active-directory.authorization-clients.graph.authorization-grant-type = authorization_code" - ) - .run(context -> - assertThrows(IllegalStateException.class, () -> context.getBean(AadAuthenticationProperties.class)) - ); + .withPropertyValues( + "spring.cloud.azure.active-directory.enabled=true", + "spring.cloud.azure.active-directory.authorization-clients.graph.authorization-grant-type=on_behalf_of", + "spring.cloud.azure.active-directory.authorization-clients.graph.authorization-grant-type = authorization_code" + ) + .run(context -> + assertThrows(IllegalStateException.class, () -> context.getBean(AadAuthenticationProperties.class)) + ); } @Test void testResourceServerWithOboExistCustomAndGraphClient() { resourceServerWithOboContextRunner() - .withPropertyValues( - "spring.cloud.azure.active-directory.enabled=true", - "spring.cloud.azure.active-directory.authorization-clients.graph.scopes=https://graph.microsoft.com/User.Read", - "spring.cloud.azure.active-directory.authorization-clients.custom.scopes=api://52261059-e515-488e-84fd-a09a3f372814/File.Read" - ) - .run(context -> { - final AadClientRegistrationRepository oboRepo = context.getBean( - AadClientRegistrationRepository.class); - final OAuth2AuthorizedClientRepository aadOboRepo = context.getBean( - OAuth2AuthorizedClientRepository.class); + .withPropertyValues( + "spring.cloud.azure.active-directory.enabled=true", + "spring.cloud.azure.active-directory.authorization-clients.graph.scopes=https://graph.microsoft.com/User.Read", + "spring.cloud.azure.active-directory.authorization-clients.custom.scopes=api://52261059-e515-488e-84fd-a09a3f372814/File.Read" + ) + .run(context -> { + final AadClientRegistrationRepository oboRepo = context.getBean( + AadClientRegistrationRepository.class); + final OAuth2AuthorizedClientRepository aadOboRepo = context.getBean( + OAuth2AuthorizedClientRepository.class); - ClientRegistration graph = oboRepo.findByRegistrationId("graph"); - ClientRegistration custom = oboRepo.findByRegistrationId("custom"); - Set graphScopes = graph.getScopes(); - Set customScopes = custom.getScopes(); + ClientRegistration graph = oboRepo.findByRegistrationId("graph"); + ClientRegistration custom = oboRepo.findByRegistrationId("custom"); + Set graphScopes = graph.getScopes(); + Set customScopes = custom.getScopes(); - assertThat(aadOboRepo).isNotNull(); - assertThat(oboRepo).isExactlyInstanceOf(AadClientRegistrationRepository.class); - assertThat(graph).isNotNull(); - assertThat(customScopes).isNotNull(); - assertThat(graphScopes).containsOnly("https://graph.microsoft.com/User.Read"); - assertThat(customScopes).containsOnly("api://52261059-e515-488e-84fd-a09a3f372814/File.Read"); - }); + assertThat(aadOboRepo).isNotNull(); + assertThat(oboRepo).isExactlyInstanceOf(AadClientRegistrationRepository.class); + assertThat(graph).isNotNull(); + assertThat(customScopes).isNotNull(); + assertThat(graphScopes).containsOnly("https://graph.microsoft.com/User.Read"); + assertThat(customScopes).containsOnly("api://52261059-e515-488e-84fd-a09a3f372814/File.Read"); + }); } @Test void defaultConverterInJwtBearerOAuth2AuthorizedClientProviderWhenNotUsingPrivateKeyJwtMethod() { resourceServerWithOboContextRunner() - .withUserConfiguration(AadOAuth2ClientConfiguration.class) - .withPropertyValues( - "spring.cloud.azure.active-directory.enabled=true", - "spring.cloud.azure.active-directory.authorization-clients.graph.scopes=https://graph.microsoft.com/User.Read", - "spring.cloud.azure.active-directory.authorization-clients.graph.authorization-grant-type=on_behalf_of", - "spring.cloud.azure.active-directory.authorization-clients.graph.scopes=api://52261059-e515-488e-84fd-a09a3f372814/File.Read" - ) - .run(context -> { - assertThat(context).doesNotHaveBean(OAuth2ClientAuthenticationJwkResolver.class); - final JwtBearerOAuth2AuthorizedClientProvider jwtBearerProvider = context.getBean( - JwtBearerOAuth2AuthorizedClientProvider.class); - final ClientRegistrationRepository clientRepository = context.getBean( - ClientRegistrationRepository.class); - MultiValueMap parameters = convertParameters(jwtBearerProvider, clientRepository); - assertThat(parameters).containsEntry("requested_token_use", Arrays.asList("on_behalf_of")); - }); + .withUserConfiguration(AadOAuth2ClientConfiguration.class) + .withPropertyValues( + "spring.cloud.azure.active-directory.enabled=true", + "spring.cloud.azure.active-directory.authorization-clients.graph.scopes=https://graph.microsoft.com/User.Read", + "spring.cloud.azure.active-directory.authorization-clients.graph.authorization-grant-type=on_behalf_of", + "spring.cloud.azure.active-directory.authorization-clients.graph.scopes=api://52261059-e515-488e-84fd-a09a3f372814/File.Read" + ) + .run(context -> { + assertThat(context).doesNotHaveBean(OAuth2ClientAuthenticationJwkResolver.class); + final JwtBearerOAuth2AuthorizedClientProvider jwtBearerProvider = context.getBean( + JwtBearerOAuth2AuthorizedClientProvider.class); + final ClientRegistrationRepository clientRepository = context.getBean( + ClientRegistrationRepository.class); + MultiValueMap parameters = convertParameters(jwtBearerProvider, clientRepository); + assertThat(parameters).containsEntry("requested_token_use", Arrays.asList("on_behalf_of")); + }); } @Test @@ -198,49 +200,77 @@ void customConverterInJwtBearerOAuth2AuthorizedClientProviderWhenUsingPrivateKey given(rsaJwk.getX509CertThumbprint()).willReturn(new Base64URL("dGVzdA")); resourceServerWithOboContextRunner() - .withBean(OAuth2ClientAuthenticationJwkResolver.class, () -> jwkResolver) - .withUserConfiguration(AadOAuth2ClientConfiguration.class) - .withPropertyValues( - "spring.cloud.azure.active-directory.enabled=true", - "spring.cloud.azure.active-directory.credential.client-certificate-path=/test/test.pfx", - "spring.cloud.azure.active-directory.credential.client-certificate-password=test", - "spring.cloud.azure.active-directory.authorization-clients.graph.scopes=https://graph.microsoft.com/User.Read", - "spring.cloud.azure.active-directory.authorization-clients.graph.authorization-grant-type=urn:ietf:params:oauth:grant-type:jwt-bearer", - "spring.cloud.azure.active-directory.authorization-clients.graph.scopes=api://52261059-e515-488e-84fd-a09a3f372814/File.Read", - "spring.cloud.azure.active-directory.authorization-clients.graph.client-authentication-method=private_key_jwt" - ) - .run(context -> { - assertThat(context).hasSingleBean(OAuth2ClientAuthenticationJwkResolver.class); - final JwtBearerOAuth2AuthorizedClientProvider jwtBearerProvider = context.getBean( - JwtBearerOAuth2AuthorizedClientProvider.class); - final ClientRegistrationRepository clientRepository = context.getBean( - ClientRegistrationRepository.class); + .withBean(OAuth2ClientAuthenticationJwkResolver.class, () -> jwkResolver) + .withUserConfiguration(AadOAuth2ClientConfiguration.class) + .withPropertyValues( + "spring.cloud.azure.active-directory.enabled=true", + "spring.cloud.azure.active-directory.credential.client-certificate-path=/test/test.pfx", + "spring.cloud.azure.active-directory.credential.client-certificate-password=test", + "spring.cloud.azure.active-directory.authorization-clients.graph.scopes=https://graph.microsoft.com/User.Read", + "spring.cloud.azure.active-directory.authorization-clients.graph.authorization-grant-type=urn:ietf:params:oauth:grant-type:jwt-bearer", + "spring.cloud.azure.active-directory.authorization-clients.graph.scopes=api://52261059-e515-488e-84fd-a09a3f372814/File.Read", + "spring.cloud.azure.active-directory.authorization-clients.graph.client-authentication-method=private_key_jwt" + ) + .run(context -> { + assertThat(context).hasSingleBean(OAuth2ClientAuthenticationJwkResolver.class); + final JwtBearerOAuth2AuthorizedClientProvider jwtBearerProvider = context.getBean( + JwtBearerOAuth2AuthorizedClientProvider.class); + final ClientRegistrationRepository clientRepository = context.getBean( + ClientRegistrationRepository.class); - MultiValueMap parameters = convertParameters(jwtBearerProvider, clientRepository); - assertThat(parameters).containsEntry("requested_token_use", Arrays.asList("on_behalf_of")); - assertThat(parameters).containsKey(OAuth2ParameterNames.CLIENT_ASSERTION_TYPE); - verify(jwkResolver).resolve(clientRepository.findByRegistrationId("graph")); - }); + MultiValueMap parameters = convertParameters(jwtBearerProvider, clientRepository); + assertThat(parameters).containsEntry("requested_token_use", Arrays.asList("on_behalf_of")); + assertThat(parameters).containsKey(OAuth2ParameterNames.CLIENT_ASSERTION_TYPE); + verify(jwkResolver).resolve(clientRepository.findByRegistrationId("graph")); + }); + } + + @Test + void restTemplateWellConfiguredWhenNotUsingPrivateKeyJwtMethod() { + webApplicationContextRunner() + .withUserConfiguration(AadOAuth2ClientConfiguration.class, RestTemplateProxyCustomizerConfiguration.class) + .run(context -> { + assertThat(context).doesNotHaveBean(OAuth2ClientAuthenticationJwkResolver.class); + assertRestTemplateWellConfigured(context); + }); + } + + @Test + void restTemplateWellConfiguredWhenUsingPrivateKeyJwtMethod() { + webApplicationContextRunner() + .withPropertyValues( + "spring.cloud.azure.active-directory.enabled=true", + "spring.cloud.azure.active-directory.credential.client-certificate-path=/test/test.pfx", + "spring.cloud.azure.active-directory.credential.client-certificate-password=test", + "spring.cloud.azure.active-directory.authorization-clients.graph.client-authentication-method=private_key_jwt", + "spring.cloud.azure.active-directory.authorization-clients.graph.scopes=https://graph.microsoft.com/User.Read", + "spring.cloud.azure.active-directory.authorization-clients.graph.scopes=api://52261059-e515-488e-84fd-a09a3f372814/File.Read" + ) + .withUserConfiguration(AadOAuth2ClientConfiguration.class, RestTemplateProxyCustomizerConfiguration.class) + .run(context -> { + assertThat(context).hasSingleBean(OAuth2ClientAuthenticationJwkResolver.class); + assertRestTemplateWellConfigured(context); + }); } @SuppressWarnings("unchecked") private MultiValueMap convertParameters(JwtBearerOAuth2AuthorizedClientProvider jwtBearerProvider, ClientRegistrationRepository clientRepository) { OAuth2AccessTokenResponseClient client = - (OAuth2AccessTokenResponseClient) ReflectionTestUtils.getField(jwtBearerProvider, "accessTokenResponseClient"); + (OAuth2AccessTokenResponseClient) ReflectionTestUtils.getField(jwtBearerProvider, "accessTokenResponseClient"); assertThat(client.getClass().getSimpleName()).isEqualTo(DefaultJwtBearerTokenResponseClient.class.getSimpleName()); JwtBearerGrantRequestEntityConverter requestEntityConverter = - (JwtBearerGrantRequestEntityConverter) ReflectionTestUtils.getField(client, "requestEntityConverter"); + (JwtBearerGrantRequestEntityConverter) ReflectionTestUtils.getField(client, "requestEntityConverter"); assertThat(requestEntityConverter.getClass().getSimpleName()).isEqualTo(AadJwtBearerGrantRequestEntityConverter.class.getSimpleName()); Converter> parametersConverter = - (Converter>) ReflectionTestUtils.getField(requestEntityConverter, "parametersConverter"); + (Converter>) ReflectionTestUtils.getField(requestEntityConverter, "parametersConverter"); JwtBearerGrantRequest request = new JwtBearerGrantRequest(clientRepository.findByRegistrationId("graph"), mock(Jwt.class)); return parametersConverter.convert(request); } - class TestOAuth2ClientAuthenticationJwkResolver implements OAuth2ClientAuthenticationJwkResolver { + static class TestOAuth2ClientAuthenticationJwkResolver implements OAuth2ClientAuthenticationJwkResolver { private final JWK jwk; diff --git a/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/useragent/CosmosUserAgentTests.java b/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/useragent/CosmosUserAgentTests.java index 187764bdd973..024c46794adf 100644 --- a/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/useragent/CosmosUserAgentTests.java +++ b/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/useragent/CosmosUserAgentTests.java @@ -9,7 +9,6 @@ import com.azure.spring.cloud.autoconfigure.context.AzureGlobalProperties; import com.azure.spring.cloud.autoconfigure.cosmos.AzureCosmosAutoConfiguration; import com.azure.spring.cloud.autoconfigure.implementation.cosmos.properties.AzureCosmosProperties; -import com.azure.spring.cloud.autoconfigure.useragent.util.UserAgentTestUtil; import com.azure.spring.cloud.core.implementation.util.AzureSpringIdentifier; import com.azure.spring.cloud.service.implementation.cosmos.CosmosClientBuilderFactory; import org.junit.jupiter.api.Assertions; @@ -17,6 +16,7 @@ import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import static com.azure.spring.cloud.core.implementation.util.ReflectionUtils.getField; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; @@ -28,23 +28,23 @@ void userAgentTest() { final CosmosClient mockCosmosClient = mock(CosmosClient.class); final CosmosAsyncClient cosmosAsyncClient = mock(CosmosAsyncClient.class); new ApplicationContextRunner() - .withConfiguration(AutoConfigurations.of(AzureCosmosAutoConfiguration.class)) - .withPropertyValues("spring.cloud.azure.cosmos.endpoint=sample") - .withBean(AzureGlobalProperties.class, AzureGlobalProperties::new) - .withBean(CosmosClient.class, () -> mockCosmosClient) - .withBean(CosmosAsyncClient.class, () -> cosmosAsyncClient) - .run(context -> { - assertThat(context).hasSingleBean(AzureCosmosProperties.class); - assertThat(context).hasSingleBean(CosmosClientBuilderFactory.class); - assertThat(context).hasSingleBean(CosmosClientBuilder.class); - assertThat(context).hasSingleBean(CosmosClient.class); - assertThat(context).hasSingleBean(CosmosAsyncClient.class); - - CosmosClientBuilder builder = context.getBean(CosmosClientBuilder.class); - String userAgent = (String) UserAgentTestUtil.getPrivateFieldValue(CosmosClientBuilder.class, "userAgentSuffix", builder); - Assertions.assertNotNull(userAgent); - Assertions.assertEquals(AzureSpringIdentifier.AZURE_SPRING_COSMOS, userAgent); - }); + .withConfiguration(AutoConfigurations.of(AzureCosmosAutoConfiguration.class)) + .withPropertyValues("spring.cloud.azure.cosmos.endpoint=sample") + .withBean(AzureGlobalProperties.class, AzureGlobalProperties::new) + .withBean(CosmosClient.class, () -> mockCosmosClient) + .withBean(CosmosAsyncClient.class, () -> cosmosAsyncClient) + .run(context -> { + assertThat(context).hasSingleBean(AzureCosmosProperties.class); + assertThat(context).hasSingleBean(CosmosClientBuilderFactory.class); + assertThat(context).hasSingleBean(CosmosClientBuilder.class); + assertThat(context).hasSingleBean(CosmosClient.class); + assertThat(context).hasSingleBean(CosmosAsyncClient.class); + + CosmosClientBuilder builder = context.getBean(CosmosClientBuilder.class); + String userAgent = (String) getField(CosmosClientBuilder.class, "userAgentSuffix", builder); + Assertions.assertNotNull(userAgent); + Assertions.assertEquals(AzureSpringIdentifier.AZURE_SPRING_COSMOS, userAgent); + }); } } diff --git a/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/useragent/amqp/EventHubsUserAgentTests.java b/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/useragent/amqp/EventHubsUserAgentTests.java index 01024fb0d899..9ffa42163c8a 100644 --- a/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/useragent/amqp/EventHubsUserAgentTests.java +++ b/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/useragent/amqp/EventHubsUserAgentTests.java @@ -12,7 +12,6 @@ import com.azure.spring.cloud.autoconfigure.context.AzureGlobalProperties; import com.azure.spring.cloud.autoconfigure.eventhubs.AzureEventHubsAutoConfiguration; import com.azure.spring.cloud.autoconfigure.eventhubs.TestCheckpointStore; -import com.azure.spring.cloud.autoconfigure.useragent.util.UserAgentTestUtil; import com.azure.spring.cloud.core.implementation.util.AzureSpringIdentifier; import com.azure.spring.cloud.service.eventhubs.consumer.EventHubsErrorHandler; import com.azure.spring.cloud.service.eventhubs.consumer.EventHubsRecordMessageListener; @@ -21,6 +20,7 @@ import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import static com.azure.spring.cloud.core.implementation.util.ReflectionUtils.getField; import static org.assertj.core.api.Assertions.assertThat; class EventHubsUserAgentTests { @@ -45,8 +45,8 @@ void userAgentTest() { assertThat(context).hasSingleBean(EventHubConsumerClient.class); EventProcessorClientBuilder eventProcessorClientBuilder = context.getBean(EventProcessorClientBuilder.class); - EventHubClientBuilder eventHubClientBuilder = (EventHubClientBuilder) UserAgentTestUtil.getPrivateFieldValue(EventProcessorClientBuilder.class, "eventHubClientBuilder", eventProcessorClientBuilder); - ClientOptions options = (ClientOptions) UserAgentTestUtil.getPrivateFieldValue(EventHubClientBuilder.class, "clientOptions", eventHubClientBuilder); + EventHubClientBuilder eventHubClientBuilder = (EventHubClientBuilder) getField(EventProcessorClientBuilder.class, "eventHubClientBuilder", eventProcessorClientBuilder); + ClientOptions options = (ClientOptions) getField(EventHubClientBuilder.class, "clientOptions", eventHubClientBuilder); Assertions.assertNotNull(options); Assertions.assertEquals(AzureSpringIdentifier.AZURE_SPRING_EVENT_HUBS, options.getApplicationId()); diff --git a/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/useragent/amqp/EventProcessorUserAgentTests.java b/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/useragent/amqp/EventProcessorUserAgentTests.java index b2da54a86cea..cd7eff1df734 100644 --- a/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/useragent/amqp/EventProcessorUserAgentTests.java +++ b/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/useragent/amqp/EventProcessorUserAgentTests.java @@ -11,7 +11,6 @@ import com.azure.spring.cloud.autoconfigure.context.AzureGlobalProperties; import com.azure.spring.cloud.autoconfigure.eventhubs.AzureEventHubsAutoConfiguration; import com.azure.spring.cloud.autoconfigure.eventhubs.TestCheckpointStore; -import com.azure.spring.cloud.autoconfigure.useragent.util.UserAgentTestUtil; import com.azure.spring.cloud.core.implementation.util.AzureSpringIdentifier; import com.azure.spring.cloud.service.eventhubs.consumer.EventHubsErrorHandler; import com.azure.spring.cloud.service.eventhubs.consumer.EventHubsRecordMessageListener; @@ -21,6 +20,7 @@ import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import static com.azure.spring.cloud.core.implementation.util.ReflectionUtils.getField; import static org.assertj.core.api.Assertions.assertThat; class EventProcessorUserAgentTests { @@ -45,8 +45,8 @@ void userAgentTest() { assertThat(context).hasSingleBean(EventProcessorClient.class); EventProcessorClientBuilder eventProcessorClientBuilder = context.getBean(EventProcessorClientBuilder.class); - EventHubClientBuilder eventHubClientBuilder = (EventHubClientBuilder) UserAgentTestUtil.getPrivateFieldValue(EventProcessorClientBuilder.class, "eventHubClientBuilder", eventProcessorClientBuilder); - ClientOptions options = (ClientOptions) UserAgentTestUtil.getPrivateFieldValue(EventHubClientBuilder.class, "clientOptions", eventHubClientBuilder); + EventHubClientBuilder eventHubClientBuilder = (EventHubClientBuilder) getField(EventProcessorClientBuilder.class, "eventHubClientBuilder", eventProcessorClientBuilder); + ClientOptions options = (ClientOptions) getField(EventHubClientBuilder.class, "clientOptions", eventHubClientBuilder); Assertions.assertNotNull(options); Assertions.assertEquals(AzureSpringIdentifier.AZURE_SPRING_EVENT_HUBS, options.getApplicationId()); diff --git a/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/useragent/amqp/ServiceBusUserAgentTests.java b/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/useragent/amqp/ServiceBusUserAgentTests.java index d588e85c82f6..bdab94fca87c 100644 --- a/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/useragent/amqp/ServiceBusUserAgentTests.java +++ b/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/useragent/amqp/ServiceBusUserAgentTests.java @@ -7,7 +7,6 @@ import com.azure.messaging.servicebus.ServiceBusClientBuilder; import com.azure.spring.cloud.autoconfigure.context.AzureGlobalProperties; import com.azure.spring.cloud.autoconfigure.servicebus.AzureServiceBusAutoConfiguration; -import com.azure.spring.cloud.autoconfigure.useragent.util.UserAgentTestUtil; import com.azure.spring.cloud.core.implementation.util.AzureSpringIdentifier; import com.azure.spring.cloud.core.provider.connectionstring.StaticConnectionStringProvider; import com.azure.spring.cloud.service.implementation.servicebus.factory.ServiceBusClientBuilderFactory; @@ -16,6 +15,7 @@ import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import static com.azure.spring.cloud.core.implementation.util.ReflectionUtils.getField; import static org.assertj.core.api.Assertions.assertThat; class ServiceBusUserAgentTests { @@ -35,7 +35,7 @@ void userAgentTest() { assertThat(context).hasSingleBean(StaticConnectionStringProvider.class); ServiceBusClientBuilder builder = context.getBean(ServiceBusClientBuilder.class); - ClientOptions options = (ClientOptions) UserAgentTestUtil.getPrivateFieldValue(ServiceBusClientBuilder.class, "clientOptions", builder); + ClientOptions options = (ClientOptions) getField(ServiceBusClientBuilder.class, "clientOptions", builder); Assertions.assertNotNull(options); Assertions.assertEquals(AzureSpringIdentifier.AZURE_SPRING_SERVICE_BUS, options.getApplicationId()); diff --git a/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/useragent/amqp/servicebus/ServiceBusProcessorUserAgentTests.java b/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/useragent/amqp/servicebus/ServiceBusProcessorUserAgentTests.java index 30527e68a6c7..59c3151338db 100644 --- a/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/useragent/amqp/servicebus/ServiceBusProcessorUserAgentTests.java +++ b/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/useragent/amqp/servicebus/ServiceBusProcessorUserAgentTests.java @@ -7,7 +7,6 @@ import com.azure.messaging.servicebus.ServiceBusClientBuilder; import com.azure.spring.cloud.autoconfigure.context.AzureGlobalProperties; import com.azure.spring.cloud.autoconfigure.servicebus.AzureServiceBusAutoConfiguration; -import com.azure.spring.cloud.autoconfigure.useragent.util.UserAgentTestUtil; import com.azure.spring.cloud.core.implementation.util.AzureSpringIdentifier; import com.azure.spring.cloud.service.implementation.servicebus.factory.ServiceBusProcessorClientBuilderFactory; import com.azure.spring.cloud.service.servicebus.consumer.ServiceBusErrorHandler; @@ -17,6 +16,7 @@ import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import static com.azure.spring.cloud.core.implementation.util.ReflectionUtils.getField; import static org.assertj.core.api.Assertions.assertThat; class ServiceBusProcessorUserAgentTests { @@ -52,8 +52,8 @@ void userAgentTest(String... propertyValues) { assertThat(context).hasSingleBean(ServiceBusClientBuilder.ServiceBusProcessorClientBuilder.class); ServiceBusClientBuilder.ServiceBusProcessorClientBuilder processorClientBuilder = context.getBean(ServiceBusClientBuilder.ServiceBusProcessorClientBuilder.class); - ServiceBusClientBuilder builder = (ServiceBusClientBuilder) UserAgentTestUtil.getPrivateFieldValue(ServiceBusClientBuilder.ServiceBusProcessorClientBuilder.class, "this$0", processorClientBuilder); - ClientOptions options = (ClientOptions) UserAgentTestUtil.getPrivateFieldValue(ServiceBusClientBuilder.class, "clientOptions", builder); + ServiceBusClientBuilder builder = (ServiceBusClientBuilder) getField(ServiceBusClientBuilder.ServiceBusProcessorClientBuilder.class, "this$0", processorClientBuilder); + ClientOptions options = (ClientOptions) getField(ServiceBusClientBuilder.class, "clientOptions", builder); Assertions.assertNotNull(options); Assertions.assertEquals(AzureSpringIdentifier.AZURE_SPRING_SERVICE_BUS, options.getApplicationId()); diff --git a/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/useragent/amqp/servicebus/ServiceBusReceiverUserAgentTests.java b/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/useragent/amqp/servicebus/ServiceBusReceiverUserAgentTests.java index ce385a702950..f1e3cf229507 100644 --- a/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/useragent/amqp/servicebus/ServiceBusReceiverUserAgentTests.java +++ b/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/useragent/amqp/servicebus/ServiceBusReceiverUserAgentTests.java @@ -7,7 +7,6 @@ import com.azure.messaging.servicebus.ServiceBusClientBuilder; import com.azure.spring.cloud.autoconfigure.context.AzureGlobalProperties; import com.azure.spring.cloud.autoconfigure.servicebus.AzureServiceBusAutoConfiguration; -import com.azure.spring.cloud.autoconfigure.useragent.util.UserAgentTestUtil; import com.azure.spring.cloud.core.implementation.util.AzureSpringIdentifier; import com.azure.spring.cloud.service.implementation.servicebus.factory.ServiceBusReceiverClientBuilderFactory; import org.junit.jupiter.api.Assertions; @@ -15,6 +14,7 @@ import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import static com.azure.spring.cloud.core.implementation.util.ReflectionUtils.getField; import static org.assertj.core.api.Assertions.assertThat; class ServiceBusReceiverUserAgentTests { @@ -48,8 +48,8 @@ void userAgentTest(String... propertyValues) { assertThat(context).hasSingleBean(ServiceBusClientBuilder.ServiceBusReceiverClientBuilder.class); ServiceBusClientBuilder.ServiceBusReceiverClientBuilder receiverClientBuilder = context.getBean(ServiceBusClientBuilder.ServiceBusReceiverClientBuilder.class); - ServiceBusClientBuilder builder = (ServiceBusClientBuilder) UserAgentTestUtil.getPrivateFieldValue(ServiceBusClientBuilder.ServiceBusReceiverClientBuilder.class, "this$0", receiverClientBuilder); - ClientOptions options = (ClientOptions) UserAgentTestUtil.getPrivateFieldValue(ServiceBusClientBuilder.class, "clientOptions", builder); + ServiceBusClientBuilder builder = (ServiceBusClientBuilder) getField(ServiceBusClientBuilder.ServiceBusReceiverClientBuilder.class, "this$0", receiverClientBuilder); + ClientOptions options = (ClientOptions) getField(ServiceBusClientBuilder.class, "clientOptions", builder); Assertions.assertNotNull(options); Assertions.assertEquals(AzureSpringIdentifier.AZURE_SPRING_SERVICE_BUS, options.getApplicationId()); diff --git a/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/useragent/amqp/servicebus/ServiceBusSenderUserAgentTests.java b/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/useragent/amqp/servicebus/ServiceBusSenderUserAgentTests.java index 011d58280abf..e8a0e6bbc4f3 100644 --- a/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/useragent/amqp/servicebus/ServiceBusSenderUserAgentTests.java +++ b/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/useragent/amqp/servicebus/ServiceBusSenderUserAgentTests.java @@ -7,7 +7,6 @@ import com.azure.messaging.servicebus.ServiceBusClientBuilder; import com.azure.spring.cloud.autoconfigure.context.AzureGlobalProperties; import com.azure.spring.cloud.autoconfigure.servicebus.AzureServiceBusAutoConfiguration; -import com.azure.spring.cloud.autoconfigure.useragent.util.UserAgentTestUtil; import com.azure.spring.cloud.core.implementation.util.AzureSpringIdentifier; import com.azure.spring.cloud.service.implementation.servicebus.factory.ServiceBusSenderClientBuilderFactory; import org.junit.jupiter.api.Assertions; @@ -15,6 +14,7 @@ import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import static com.azure.spring.cloud.core.implementation.util.ReflectionUtils.getField; import static org.assertj.core.api.Assertions.assertThat; class ServiceBusSenderUserAgentTests { @@ -48,8 +48,8 @@ void userAgentTest(String... propertyValues) { assertThat(context).hasSingleBean(ServiceBusClientBuilder.ServiceBusSenderClientBuilder.class); ServiceBusClientBuilder.ServiceBusSenderClientBuilder serviceBusSenderClientBuilder = context.getBean(ServiceBusClientBuilder.ServiceBusSenderClientBuilder.class); - ServiceBusClientBuilder builder = (ServiceBusClientBuilder) UserAgentTestUtil.getPrivateFieldValue(ServiceBusClientBuilder.ServiceBusSenderClientBuilder.class, "this$0", serviceBusSenderClientBuilder); - ClientOptions options = (ClientOptions) UserAgentTestUtil.getPrivateFieldValue(ServiceBusClientBuilder.class, "clientOptions", builder); + ServiceBusClientBuilder builder = (ServiceBusClientBuilder) getField(ServiceBusClientBuilder.ServiceBusSenderClientBuilder.class, "this$0", serviceBusSenderClientBuilder); + ClientOptions options = (ClientOptions) getField(ServiceBusClientBuilder.class, "clientOptions", builder); Assertions.assertNotNull(options); Assertions.assertEquals(AzureSpringIdentifier.AZURE_SPRING_SERVICE_BUS, options.getApplicationId()); diff --git a/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/useragent/amqp/servicebus/ServiceBusSessionProcessorUserAgentTests.java b/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/useragent/amqp/servicebus/ServiceBusSessionProcessorUserAgentTests.java index 13eb94575ee8..190604a0cc90 100644 --- a/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/useragent/amqp/servicebus/ServiceBusSessionProcessorUserAgentTests.java +++ b/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/useragent/amqp/servicebus/ServiceBusSessionProcessorUserAgentTests.java @@ -7,7 +7,6 @@ import com.azure.messaging.servicebus.ServiceBusClientBuilder; import com.azure.spring.cloud.autoconfigure.context.AzureGlobalProperties; import com.azure.spring.cloud.autoconfigure.servicebus.AzureServiceBusAutoConfiguration; -import com.azure.spring.cloud.autoconfigure.useragent.util.UserAgentTestUtil; import com.azure.spring.cloud.core.implementation.util.AzureSpringIdentifier; import com.azure.spring.cloud.service.implementation.servicebus.factory.ServiceBusSessionProcessorClientBuilderFactory; import com.azure.spring.cloud.service.servicebus.consumer.ServiceBusErrorHandler; @@ -17,6 +16,7 @@ import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import static com.azure.spring.cloud.core.implementation.util.ReflectionUtils.getField; import static org.assertj.core.api.Assertions.assertThat; class ServiceBusSessionProcessorUserAgentTests { @@ -54,8 +54,8 @@ void userAgentTest(String... propertyValues) { assertThat(context).hasSingleBean(ServiceBusClientBuilder.ServiceBusSessionProcessorClientBuilder.class); ServiceBusClientBuilder.ServiceBusSessionProcessorClientBuilder processorClientBuilder = context.getBean(ServiceBusClientBuilder.ServiceBusSessionProcessorClientBuilder.class); - ServiceBusClientBuilder builder = (ServiceBusClientBuilder) UserAgentTestUtil.getPrivateFieldValue(ServiceBusClientBuilder.ServiceBusSessionProcessorClientBuilder.class, "this$0", processorClientBuilder); - ClientOptions options = (ClientOptions) UserAgentTestUtil.getPrivateFieldValue(ServiceBusClientBuilder.class, "clientOptions", builder); + ServiceBusClientBuilder builder = (ServiceBusClientBuilder) getField(ServiceBusClientBuilder.ServiceBusSessionProcessorClientBuilder.class, "this$0", processorClientBuilder); + ClientOptions options = (ClientOptions) getField(ServiceBusClientBuilder.class, "clientOptions", builder); Assertions.assertNotNull(options); Assertions.assertEquals(AzureSpringIdentifier.AZURE_SPRING_SERVICE_BUS, options.getApplicationId()); diff --git a/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/useragent/amqp/servicebus/ServiceBusSessionReceiverUserAgentTests.java b/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/useragent/amqp/servicebus/ServiceBusSessionReceiverUserAgentTests.java index ff0fd587345d..78ea3e00fb55 100644 --- a/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/useragent/amqp/servicebus/ServiceBusSessionReceiverUserAgentTests.java +++ b/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/useragent/amqp/servicebus/ServiceBusSessionReceiverUserAgentTests.java @@ -7,7 +7,6 @@ import com.azure.messaging.servicebus.ServiceBusClientBuilder; import com.azure.spring.cloud.autoconfigure.context.AzureGlobalProperties; import com.azure.spring.cloud.autoconfigure.servicebus.AzureServiceBusAutoConfiguration; -import com.azure.spring.cloud.autoconfigure.useragent.util.UserAgentTestUtil; import com.azure.spring.cloud.core.implementation.util.AzureSpringIdentifier; import com.azure.spring.cloud.service.implementation.servicebus.factory.ServiceBusSessionReceiverClientBuilderFactory; import org.junit.jupiter.api.Assertions; @@ -15,6 +14,7 @@ import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import static com.azure.spring.cloud.core.implementation.util.ReflectionUtils.getField; import static org.assertj.core.api.Assertions.assertThat; class ServiceBusSessionReceiverUserAgentTests { @@ -50,8 +50,8 @@ void userAgentTest(String... propertyValues) { assertThat(context).hasSingleBean(ServiceBusClientBuilder.ServiceBusSessionReceiverClientBuilder.class); ServiceBusClientBuilder.ServiceBusSessionReceiverClientBuilder receiverClientBuilder = context.getBean(ServiceBusClientBuilder.ServiceBusSessionReceiverClientBuilder.class); - ServiceBusClientBuilder builder = (ServiceBusClientBuilder) UserAgentTestUtil.getPrivateFieldValue(ServiceBusClientBuilder.ServiceBusSessionReceiverClientBuilder.class, "this$0", receiverClientBuilder); - ClientOptions options = (ClientOptions) UserAgentTestUtil.getPrivateFieldValue(ServiceBusClientBuilder.class, "clientOptions", builder); + ServiceBusClientBuilder builder = (ServiceBusClientBuilder) getField(ServiceBusClientBuilder.ServiceBusSessionReceiverClientBuilder.class, "this$0", receiverClientBuilder); + ClientOptions options = (ClientOptions) getField(ServiceBusClientBuilder.class, "clientOptions", builder); Assertions.assertNotNull(options); Assertions.assertEquals(AzureSpringIdentifier.AZURE_SPRING_SERVICE_BUS, options.getApplicationId()); diff --git a/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/useragent/util/UserAgentTestUtil.java b/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/useragent/util/UserAgentTestUtil.java index 65118504df44..99eee973c70a 100644 --- a/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/useragent/util/UserAgentTestUtil.java +++ b/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/useragent/util/UserAgentTestUtil.java @@ -8,25 +8,15 @@ import com.azure.core.http.policy.HttpPipelinePolicy; import com.azure.core.http.policy.UserAgentPolicy; -import java.lang.reflect.Field; +import static com.azure.spring.cloud.core.implementation.util.ReflectionUtils.getField; public class UserAgentTestUtil { - public static Object getPrivateFieldValue(Class clazz, String fieldName, Object object) { - try { - Field field = clazz.getDeclaredField(fieldName); - field.setAccessible(true); - return field.get(object); - } catch (NoSuchFieldException | IllegalAccessException e) { - return null; - } - } - public static String getUserAgent(HttpPipeline pipeline) { for (int i = 0; i < pipeline.getPolicyCount(); i++) { HttpPipelinePolicy policy = pipeline.getPolicy(i); if (policy instanceof UserAgentPolicy) { - return (String) getPrivateFieldValue(UserAgentPolicy.class, "userAgent", policy); + return (String) getField(UserAgentPolicy.class, "userAgent", policy); } } return null; From fab38f385d6dcf8ad86f756587b773de3a3a29f3 Mon Sep 17 00:00:00 2001 From: Rujun Chen Date: Fri, 21 Oct 2022 17:15:35 +0800 Subject: [PATCH 4/8] Fix #31482 rest template used to get access token should only contain 2 converters. --- .../AadRestTemplateCreator.java | 24 ++++--------------- .../resources/aad/access-token-response.json | 9 +++++++ 2 files changed, 14 insertions(+), 19 deletions(-) create mode 100644 sdk/spring/spring-cloud-azure-autoconfigure/src/test/resources/aad/access-token-response.json 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/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..2c9f94ceedc2 --- /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" +} From 067cc2f1e2a6d7f685d63b4752106d8754f2a77f Mon Sep 17 00:00:00 2001 From: Rujun Chen Date: Fri, 21 Oct 2022 17:19:08 +0800 Subject: [PATCH 5/8] Fix #31233: NimbusJwtDecoder still uses RestTemplate() instead RestTemplateBuilder --- .../AadResourceServerConfiguration.java | 7 ++-- .../webapp/AadOidcIdTokenDecoderFactory.java | 42 +++++++++++++++++++ 2 files changed, 46 insertions(+), 3 deletions(-) create mode 100644 sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/aad/implementation/webapp/AadOidcIdTokenDecoderFactory.java diff --git a/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/aad/configuration/AadResourceServerConfiguration.java b/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/aad/configuration/AadResourceServerConfiguration.java index c43aab2860e6..e6ad19bd01ee 100644 --- a/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/aad/configuration/AadResourceServerConfiguration.java +++ b/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/aad/configuration/AadResourceServerConfiguration.java @@ -65,9 +65,10 @@ public AadResourceServerConfiguration(RestTemplateBuilder restTemplateBuilder) { @ConditionalOnMissingBean(JwtDecoder.class) public JwtDecoder jwtDecoder(AadAuthenticationProperties aadAuthenticationProperties) { AadAuthorizationServerEndpoints identityEndpoints = new AadAuthorizationServerEndpoints( - aadAuthenticationProperties.getProfile().getEnvironment().getActiveDirectoryEndpoint(), aadAuthenticationProperties.getProfile().getTenantId()); + aadAuthenticationProperties.getProfile().getEnvironment().getActiveDirectoryEndpoint(), + aadAuthenticationProperties.getProfile().getTenantId()); NimbusJwtDecoder nimbusJwtDecoder = NimbusJwtDecoder - .withJwkSetUri(identityEndpoints.getJwkSetEndpoint()) + .withJwkSetUri(identityEndpoints.getJwkSetEndpoint()) .restOperations(createRestTemplate(restTemplateBuilder)) .build(); List> validators = createDefaultValidator(aadAuthenticationProperties); @@ -106,7 +107,7 @@ public List> createDefaultValidator(AadAuthenticationP @ConditionalOnMissingBean(WebSecurityConfigurerAdapter.class) @ConditionalOnExpression("!'${spring.cloud.azure.active-directory.application-type}'.equalsIgnoreCase('web_application_and_resource_server')") public static class DefaultAadResourceServerWebSecurityConfigurerAdapter extends - AadResourceServerWebSecurityConfigurerAdapter { + AadResourceServerWebSecurityConfigurerAdapter { /** * configure diff --git a/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/aad/implementation/webapp/AadOidcIdTokenDecoderFactory.java b/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/aad/implementation/webapp/AadOidcIdTokenDecoderFactory.java new file mode 100644 index 000000000000..37706fb26f0a --- /dev/null +++ b/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/aad/implementation/webapp/AadOidcIdTokenDecoderFactory.java @@ -0,0 +1,42 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.spring.cloud.autoconfigure.aad.implementation.webapp; + +import org.springframework.security.oauth2.client.registration.ClientRegistration; +import org.springframework.security.oauth2.core.oidc.OidcIdToken; +import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm; +import org.springframework.security.oauth2.jwt.JwtDecoder; +import org.springframework.security.oauth2.jwt.JwtDecoderFactory; +import org.springframework.security.oauth2.jwt.NimbusJwtDecoder; +import org.springframework.web.client.RestOperations; + +/** + * A factory that provides a {@link JwtDecoder} used for {@link OidcIdToken} signature verification. + * + * @see azure-active-directory id-tokens + */ +public class AadOidcIdTokenDecoderFactory implements JwtDecoderFactory { + + private final JwtDecoder jwtDecoder; + + /** + * + * @param jwkSetUri The uri of the jwk set. For example: + * + * https://login.microsoftonline.com/common/discovery/v2.0/keys + * @param restOperations The RestOperations used to retrieve jwk from jwkSetUri. + */ + public AadOidcIdTokenDecoderFactory(String jwkSetUri, RestOperations restOperations) { + this.jwtDecoder = NimbusJwtDecoder + .withJwkSetUri(jwkSetUri) + .jwsAlgorithm(SignatureAlgorithm.RS256) + .restOperations(restOperations) + .build(); + } + + @Override + public JwtDecoder createDecoder(ClientRegistration context) { + return jwtDecoder; + } +} From 0cea6c99ad8f002b534f4d4c822401de4e186071 Mon Sep 17 00:00:00 2001 From: Rujun Chen Date: Fri, 21 Oct 2022 17:31:17 +0800 Subject: [PATCH 6/8] Fix #31593: Proxy setting not work in Azure AD B2C web application --- .vscode/cspell.json | 7 + .../src/main/resources/revapi/revapi.json | 5 + sdk/spring/CHANGELOG.md | 44 +++--- .../aadb2c/AadB2cAutoConfiguration.java | 37 ++++- .../aadb2c/AadB2cOidcLoginConfigurer.java | 47 ++++-- .../AadB2cOidcIdTokenDecoderFactory.java | 50 +++++++ .../aad/RestTemplateTestUtil.java | 134 ++++++++++++++++++ .../AadB2cAutoConfigurationTests.java | 39 ++++- 8 files changed, 326 insertions(+), 37 deletions(-) create mode 100644 sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/aadb2c/implementation/AadB2cOidcIdTokenDecoderFactory.java create mode 100644 sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/aad/RestTemplateTestUtil.java diff --git a/.vscode/cspell.json b/.vscode/cspell.json index 55091e4d38fc..48b9b2bd65f6 100644 --- a/.vscode/cspell.json +++ b/.vscode/cspell.json @@ -248,6 +248,13 @@ "compilerArguments", "annotationProcessors" ] + }, + { + "filename": "sdk/spring/CHANGELOG.md", + "words": [ + "AADB", + "JPMS" + ] } ], "allowCompoundWords": false diff --git a/eng/code-quality-reports/src/main/resources/revapi/revapi.json b/eng/code-quality-reports/src/main/resources/revapi/revapi.json index 237888c5c8a6..c6d782def152 100644 --- a/eng/code-quality-reports/src/main/resources/revapi/revapi.json +++ b/eng/code-quality-reports/src/main/resources/revapi/revapi.json @@ -501,6 +501,11 @@ "code": "java.method.numberOfParametersChanged", "new": "method void com.azure.spring.cloud.autoconfigure.aadb2c.configuration.AadB2cOAuth2ClientConfiguration::(com.azure.spring.cloud.autoconfigure.aadb2c.properties.AadB2cProperties, org.springframework.boot.web.client.RestTemplateBuilder)", "justification": "Bean configuration will not be used by customer in java code." + }, + { + "code": "java.method.numberOfParametersChanged", + "new": "method void com.azure.spring.cloud.autoconfigure.aadb2c.AadB2cAutoConfiguration::(org.springframework.boot.web.client.RestTemplateBuilder)", + "justification": "Bean configuration will not be used by customer in java code." } ] } diff --git a/sdk/spring/CHANGELOG.md b/sdk/spring/CHANGELOG.md index c94e61142508..41971dd4f8be 100644 --- a/sdk/spring/CHANGELOG.md +++ b/sdk/spring/CHANGELOG.md @@ -40,8 +40,6 @@ This section includes changes in `spring-cloud-azure-autoconfigure` module. - Fix the Service Bus Binder cannot automatically create Topic/Subscriptions from consumer bug. [#30722](https://github.com/Azure/azure-sdk-for-java/pull/30722). - Fix the cloud type cannot be configured for Service Bus Binder bug [#30936](https://github.com/Azure/azure-sdk-for-java/issues/30936). - - ## 4.3.0 (2022-06-29) - This release is compatible with Spring Boot 2.5.0-2.5.14, 2.6.0-2.6.9, 2.7.0-2.7.1. (Note: 2.5.x (x>14), 2.6.y (y>9) and 2.7.z (z>1) should be supported, but they aren't tested with this release.) - This release is compatible with Spring Cloud 2020.0.3-2020.0.5, 2021.0.0-2021.0.3. (Note: 2020.0.x (x>5) and 2021.0.y (y>3) should be supported, but they aren't tested with this release.) @@ -138,7 +136,7 @@ This section includes changes in `spring-integration-azure-storage-queue` module - Upgrade Spring Boot to 2.6.6 to address [CVE-2022-22965](https://github.com/advisories/GHSA-36p3-wjmg-h94x) [#28280](https://github.com/Azure/azure-sdk-for-java/pull/28280). ### Features Added -- GA the `spring-cloud-azure-starter-keyvault-certificates`. This starter supports the auto-configuration of Azure Key Vault `CertificateClient` and `CertificateAsyncClient`. +- GA the `spring-cloud-azure-starter-keyvault-certificates`. This starter supports the auto-configuration of Azure Key Vault `CertificateClient` and `CertificateAsyncClient`. ### Spring Cloud Azure Dependencies (BOM) #### Dependency Updates @@ -161,8 +159,8 @@ This section includes changes in `spring-cloud-azure-autoconfigure` module. ### Dependency Updates - Upgrade dependency according to spring-boot-dependencies:2.6.3 and spring-cloud-dependencies:2021.0.0. -### Features Added -- Add `Automatic-Module-Name` for all Spring Cloud Azure modules and change the root pacakge names to match the module names [#27350](https://github.com/Azure/azure-sdk-for-java/issues/27350), [#27420](https://github.com/Azure/azure-sdk-for-java/pull/27420). +### Features Added +- Add `Automatic-Module-Name` for all Spring Cloud Azure modules and change the root package names to match the module names [#27350](https://github.com/Azure/azure-sdk-for-java/issues/27350), [#27420](https://github.com/Azure/azure-sdk-for-java/pull/27420). ### Spring Cloud Azure Dependencies (BOM) #### Dependency Updates @@ -203,8 +201,8 @@ This section includes changes in `spring-cloud-azure-starter-active-directory` m + Delete `AadJwtAudienceValidator` and use `JwtClaimValidator` instead. + Rename `AadTokenClaim` to `AadJwtClaimNames`. -#### Features Added -- Support constructing `AadOAuth2AuthorizationRequestResolver` with `authorizationRequestBaseUri` [#26494](https://github.com/Azure/azure-sdk-for-java/issues/26494). +#### Features Added +- Support constructing `AadOAuth2AuthorizationRequestResolver` with `authorizationRequestBaseUri` [#26494](https://github.com/Azure/azure-sdk-for-java/issues/26494). - Make `AadWebSecurityConfigurerAdapter` more configurable [#27802](https://github.com/Azure/azure-sdk-for-java/pull/27802). #### Dependency Updates @@ -217,21 +215,21 @@ This section includes changes in `spring-cloud-azure-autoconfigure` module. - Refactor retry options [#27332](https://github.com/Azure/azure-sdk-for-java/pull/27332), [#27586](https://github.com/Azure/azure-sdk-for-java/pull/27586). + Delete properties `spring.cloud.azure.retry.timeout` and `spring.cloud.azure..retry.timeout`. + Add properties `spring.cloud.azure.retry.amqp.try-timeout` and `spring.cloud.azure..retry.try-timeout` instead. (`` means this option only applies to AMQP-based service clients). - + Delete properties `spring.cloud.azure.retry.back-off.max-attempts`, `spring.cloud.azure.retry.back-off.delay`, `spring.cloud.azure.retry.back-off.max-delay`, and `spring.cloud.azure.retry.backoff.multiplier`. - + Delete properties `spring.cloud.azure..retry.back-off.max-attempts`, `spring.cloud.azure..retry.back-off.delay`, `spring.cloud.azure..retry.back-off..max-delay`, and `spring.cloud.azure..retry.backoff.multiplier`. + + Delete properties `spring.cloud.azure.retry.back-off.max-attempts`, `spring.cloud.azure.retry.back-off.delay`, `spring.cloud.azure.retry.back-off.max-delay`, and `spring.cloud.azure.retry.backoff.multiplier`. + + Delete properties `spring.cloud.azure..retry.back-off.max-attempts`, `spring.cloud.azure..retry.back-off.delay`, `spring.cloud.azure..retry.back-off..max-delay`, and `spring.cloud.azure..retry.backoff.multiplier`. + Add properties `spring.cloud.azure.retry.mode`, `spring.cloud.azure..retry.mode`, `spring.cloud.azure.retry.exponential.*`, `spring.cloud.azure..retry.exponential.*`, `spring.cloud.azure.retry.fixed*`, and `spring.cloud.azure..retry.fixed.*` instead: - `spring.cloud.azure.retry.exponential.base-delay`. - `spring.cloud.azure.retry.exponential.max-delay`. - `spring.cloud.azure.retry.exponential.max-retries`. - `spring.cloud.azure.retry.fixed.delay`. - `spring.cloud.azure.retry.fixed.max-retries`. -- Refactor proxy options [#27402](https://github.com/Azure/azure-sdk-for-java/pull/27402): +- Refactor proxy options [#27402](https://github.com/Azure/azure-sdk-for-java/pull/27402): + Change `spring.cloud.azure..proxy.authentication-type` to `spring.cloud.azure..proxy.authentication-type`. (`` means this property only applies to AMQP-based service clients). + Delete `spring.cloud.azure.proxy.authentication-type` and add `spring.cloud.azure.proxy.amqp.authentication-type` instead. -- Refactor client options [#27402](https://github.com/Azure/azure-sdk-for-java/pull/27511): +- Refactor client options [#27402](https://github.com/Azure/azure-sdk-for-java/pull/27511): + Change `spring.cloud.azure..client.headers` to `spring.cloud.azure..client.headers`. (`` means this property only applies to HTTP-based service clients). + Delete `spring.cloud.azure.client.headers` and add `spring.cloud.azure.client.http.headers` instead. -- Rename properties `spring.cloud.azure.profile.cloud` and `spring.cloud.azure..cloud` to `spring.cloud.azure.profile.cloud-type` and `spring.cloud.azure..cloud-type` [#27258](https://github.com/Azure/azure-sdk-for-java/pull/27258). +- Rename properties `spring.cloud.azure.profile.cloud` and `spring.cloud.azure..cloud` to `spring.cloud.azure.profile.cloud-type` and `spring.cloud.azure..cloud-type` [#27258](https://github.com/Azure/azure-sdk-for-java/pull/27258). - Delete properties `spring.cloud.azure.credential.managed-identity-client-id` and `spring.cloud.azure..credential.managed-identity-client-id`. Add `spring.cloud.azure.credential.managed-identity-enabled` and `spring.cloud.azure..credential.managed-identity-enabled` instead [#27118](https://github.com/Azure/azure-sdk-for-java/pull/27118), [#27258](https://github.com/Azure/azure-sdk-for-java/pull/27258). - Change type of JWK/JWT time duration properties from `int/long` to `Duration` [#27579](https://github.com/Azure/azure-sdk-for-java/pull/27579): + `spring.cloud.azure.active-directory.jwt-connect-timeout` and `spring.cloud.azure.active-directory.b2c.jwt-connect-timeout`. @@ -264,18 +262,18 @@ This section includes changes in `spring-cloud-azure-autoconfigure` module. - Delete `EventHubsInitializationContextConsumer`, `EventHubsCloseContextConsumer`, `EventHubsErrorContextConsumer` and `ServiceBusErrorContextConsumer`. Please use `Consumer<>` directly if you want to configure them [#27288](https://github.com/Azure/azure-sdk-for-java/pull/27288). - Delete the bean of `EventHubsProcessorContainer` in the autoconfiguration for Event Hubs Spring Messaging support. When needed, a user-defined `EventHubsMessageListenerContainer` bean should be provided for the replacement [#27216](https://github.com/Azure/azure-sdk-for-java/pull/27216). - Delete the bean of `ServiceBusProcessorContainer` in the autoconfiguration for Service Bus Spring Messaging support. When needed, a user-defined `ServiceBusMessageListenerContainer` bean should be provided for the replacement [#27216](https://github.com/Azure/azure-sdk-for-java/pull/27216). -- Rename Beans in `AadAuthenticationFilterAutoConfiguration` from `azureADJwtTokenFilter\getJWTResourceRetriever\getJWKSetCache` to `aadAuthenticationFilter\jwtResourceRetriever\jwkSetCache` [#27301](https://github.com/Azure/azure-sdk-for-java/pull/27301). -- Rename Bean in `AadB2cResourceServerAutoConfiguration` from `aadIssuerJWSKeySelector` to `aadIssuerJwsKeySelector` [#27301](https://github.com/Azure/azure-sdk-for-java/pull/27301). +- Rename Beans in `AadAuthenticationFilterAutoConfiguration` from `azureADJwtTokenFilter\getJWTResourceRetriever\getJWKSetCache` to `aadAuthenticationFilter\jwtResourceRetriever\jwkSetCache` [#27301](https://github.com/Azure/azure-sdk-for-java/pull/27301). +- Rename Bean in `AadB2cResourceServerAutoConfiguration` from `aadIssuerJWSKeySelector` to `aadIssuerJwsKeySelector` [#27301](https://github.com/Azure/azure-sdk-for-java/pull/27301). - Change non-SDK defined boolean configuration properties from `Boolean` to `boolean` [#27321](https://github.com/Azure/azure-sdk-for-java/pull/27321). - Delete unused API from `KeyVaultOperation` and `KeyVaultPropertySource` [#27722](https://github.com/Azure/azure-sdk-for-java/pull/27722). - Delete `Propagator` from the constructor of `SleuthHttpPolicy` [#27621](https://github.com/Azure/azure-sdk-for-java/pull/27621). -- Move classes for internal usage to the implementation pacakge [#27113](https://github.com/Azure/azure-sdk-for-java/issues/27113). +- Move classes for internal usage to the implementation package [#27113](https://github.com/Azure/azure-sdk-for-java/issues/27113). #### Features Added - Add a compatibility verifier for Spring Cloud Azure [#25437](https://github.com/Azure/azure-sdk-for-java/issues/25437). - Support configuring an `AzureTokenCredentialResolver` for each `*ClientBuilderFactory` [#26792](https://github.com/Azure/azure-sdk-for-java/pull/26792). - Add more hints for configuration properties in `additional-spring-configuration-metadata.json` file [#26600](https://github.com/Azure/azure-sdk-for-java/issues/26600). -- Add descriptions and logs for `namespacce` property of Service Bus and Event Hubs [#27053](https://github.com/Azure/azure-sdk-for-java/issues/27053). +- Add descriptions and logs for `namespace` property of Service Bus and Event Hubs [#27053](https://github.com/Azure/azure-sdk-for-java/issues/27053). #### Bugs Fixed - Fix AAD autoconfiguration activated when no web dependencies on the classpath [#26915](https://github.com/Azure/azure-sdk-for-java/issues/26915). @@ -340,7 +338,7 @@ This section includes changes in `spring-cloud-azure-stream-binder-servicebus` m This section includes changes in the `spring-integration-azure-eventhubs` module. #### Breaking Changes -- Move classes for internal usage to the implementation pacakge [#27281](https://github.com/Azure/azure-sdk-for-java/pull/27281). +- Move classes for internal usage to the implementation package [#27281](https://github.com/Azure/azure-sdk-for-java/pull/27281). - Change the message header prefix from `azure_eventhub` to `azure_eventhubs_` [#27746](https://github.com/Azure/azure-sdk-for-java/pull/27746). - Refactor the constructors of `EventHubsInboundChannelAdapter` to `EventHubsInboundChannelAdapter(EventHubsMessageListenerContainer)` and `EventHubsInboundChannelAdapter(EventHubsMessageListenerContainer, ListenerMode)` [#27216](https://github.com/Azure/azure-sdk-for-java/pull/27216), [#27421](https://github.com/Azure/azure-sdk-for-java/pull/27421). @@ -348,7 +346,7 @@ This section includes changes in the `spring-integration-azure-eventhubs` module This section includes changes in the `spring-integration-azure-servicebus` module. #### Breaking Changes -- Move classes for internal usage to the implementation pacakge [#27281](https://github.com/Azure/azure-sdk-for-java/pull/27281). +- Move classes for internal usage to the implementation package [#27281](https://github.com/Azure/azure-sdk-for-java/pull/27281). - Delete message header of `AzureHeaders.RAW_ID`. Please use `ServiceBusMessageHeaders.MESSAGE_ID` instead [#27675](https://github.com/Azure/azure-sdk-for-java/pull/27675). - Delete class `CheckpointConfig`. Please use `ServiceBusContainerProperties#setAutoComplete` instead. To disable the auto-complete mode is equivalent to `MANUAL` checkpoint mode and to enable it will trigger the `RECORD` mode [#27615](https://github.com/Azure/azure-sdk-for-java/pull/27615), [#27646](https://github.com/Azure/azure-sdk-for-java/pull/27646). @@ -385,18 +383,18 @@ This section includes changes in the `spring-messaging-azure` module. This section includes changes in the `spring-messaging-azure-eventhubs` module. #### Breaking Changes -- Move classes for internal usage to the implementation pacakge [#27396](https://github.com/Azure/azure-sdk-for-java/pull/27396). +- Move classes for internal usage to the implementation package [#27396](https://github.com/Azure/azure-sdk-for-java/pull/27396). - Move class `PartitionSupplier` from package `com.azure.spring.messaging` to `com.azure.spring.messaging.eventhubs.core` [#27422](https://github.com/Azure/azure-sdk-for-java/issues/27422). - Delete parameter of `PartitionSupplier` from the sending API for a single message in `EventHubsTemplate` [#27422](https://github.com/Azure/azure-sdk-for-java/pull/27422). Please use message headers of `com.azure.spring.messaging.AzureHeaders.PARTITION_ID` and `com.azure.spring.messaging.AzureHeaders.PARTITION_KEY` instead [#27422](https://github.com/Azure/azure-sdk-for-java/issues/27422). - Change the message header prefix from `azure_eventhub` to `azure_eventhubs_` [#27746](https://github.com/Azure/azure-sdk-for-java/pull/27746). -- Refactor the `EventHubsMessageListenerContainer` [#27216](https://github.com/Azure/azure-sdk-for-java/pull/27216), [#27543](https://github.com/Azure/azure-sdk-for-java/pull/27543): +- Refactor the `EventHubsMessageListenerContainer` [#27216](https://github.com/Azure/azure-sdk-for-java/pull/27216), [#27543](https://github.com/Azure/azure-sdk-for-java/pull/27543): + Change `EventHubsProcessorContainer` to `EventHubsMessageListenerContainer`. + Add class `EventHubsContainerProperties` for constructing a `EventHubsMessageListenerContainer`. + Add `EventHubsErrorHandler` for `EventHubsMessageListenerContainer`. + Rename `BatchEventProcessingListener` and `RecordEventProcessingListener` to `EventHubsBatchMessageListener` and `EventHubsRecordMessageListener`. #### Features Added -- Support adding builder custoimzers in `DefaultEventHubsNamespaceProducerFactory` and `DefaultEventHubsNamespaceProcessorFactory` [#27452](https://github.com/Azure/azure-sdk-for-java/pull/27452). +- Support adding builder customizers in `DefaultEventHubsNamespaceProducerFactory` and `DefaultEventHubsNamespaceProcessorFactory` [#27452](https://github.com/Azure/azure-sdk-for-java/pull/27452). ### Spring Messaging Azure Service Bus This section includes changes in the `spring-messaging-azure-servicebus` module. @@ -405,7 +403,7 @@ This section includes changes in the `spring-messaging-azure-servicebus` module. - Delete parameter of `PartitionSupplier` from the sending API for a single message in `ServiceBusTemplate` [#27349](https://github.com/Azure/azure-sdk-for-java/issues/27349). Please use message header of `com.azure.spring.messaging.AzureHeaders.PARTITION_KEY` instead [#27422](https://github.com/Azure/azure-sdk-for-java/issues/27422). - Delete message header of `AzureHeaders.RAW_ID`. Please use `ServiceBusMessageHeaders.MESSAGE_ID` instead [#27675](https://github.com/Azure/azure-sdk-for-java/pull/27675), [#27820](https://github.com/Azure/azure-sdk-for-java/pull/27820). -- Refactor the `ServiceBusMessageListenerContainer` [#27216](https://github.com/Azure/azure-sdk-for-java/pull/27216), [#27543](https://github.com/Azure/azure-sdk-for-java/pull/27543): +- Refactor the `ServiceBusMessageListenerContainer` [#27216](https://github.com/Azure/azure-sdk-for-java/pull/27216), [#27543](https://github.com/Azure/azure-sdk-for-java/pull/27543): + Change `ServiceBusProcessorContainer` to `ServiceBusMessageListenerContainer`. + Add class `ServiceBusContainerProperties` for constructing a `ServiceBusMessageListenerContainer`. + Add `ServiceBusErrorHandler` for `ServiceBusMessageListenerContainer`. @@ -429,7 +427,7 @@ Please use message header of `com.azure.spring.messaging.AzureHeaders.PARTITION_ * ServiceBusMessageHeaders.STATE * ServiceBusMessageHeaders.SUBJECT - Support the message header of `ServiceBusMessageHeaders.SUBJECT` to specify the AMQP property of `subject` when sending messages [#27675](https://github.com/Azure/azure-sdk-for-java/pull/27675). -- Support adding builder custoimzers in `DefaultServiceBusNamespaceProducerFactory` and `DefaultServiceBusNamespaceProcessorFactory` [#27452](https://github.com/Azure/azure-sdk-for-java/pull/27452), [#27820](https://github.com/Azure/azure-sdk-for-java/pull/27820). +- Support adding builder customizers in `DefaultServiceBusNamespaceProducerFactory` and `DefaultServiceBusNamespaceProcessorFactory` [#27452](https://github.com/Azure/azure-sdk-for-java/pull/27452), [#27820](https://github.com/Azure/azure-sdk-for-java/pull/27820). ### Spring Messaging Azure Storage Queue This section includes changes in `spring-messaging-azure-storage-queue` module. diff --git a/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/aadb2c/AadB2cAutoConfiguration.java b/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/aadb2c/AadB2cAutoConfiguration.java index b1938f6b75b5..b4d6fafb724b 100644 --- a/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/aadb2c/AadB2cAutoConfiguration.java +++ b/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/aadb2c/AadB2cAutoConfiguration.java @@ -2,18 +2,28 @@ // Licensed under the MIT License. package com.azure.spring.cloud.autoconfigure.aadb2c; +import com.azure.spring.cloud.autoconfigure.aad.configuration.AadOAuth2ClientConfiguration; import com.azure.spring.cloud.autoconfigure.aadb2c.configuration.AadB2cOAuth2ClientConfiguration; import com.azure.spring.cloud.autoconfigure.aadb2c.configuration.AadB2cPropertiesConfiguration; import com.azure.spring.cloud.autoconfigure.aadb2c.implementation.AadB2cConditions; +import com.azure.spring.cloud.autoconfigure.aadb2c.implementation.AadB2cOidcIdTokenDecoderFactory; import com.azure.spring.cloud.autoconfigure.aadb2c.properties.AadB2cProperties; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; +import org.springframework.boot.web.client.RestTemplateBuilder; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; +import org.springframework.security.config.annotation.web.configurers.oauth2.client.OAuth2LoginConfigurer; +import org.springframework.security.oauth2.client.registration.ClientRegistration; import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; +import org.springframework.security.oauth2.jwt.JwtDecoder; +import org.springframework.security.oauth2.jwt.JwtDecoderFactory; +import org.springframework.web.client.RestTemplate; + +import static com.azure.spring.cloud.autoconfigure.aad.implementation.AadRestTemplateCreator.createRestTemplate; /** * Configure the necessary beans for AAD B2C authentication and authorization, @@ -26,6 +36,17 @@ @Import({ AadB2cPropertiesConfiguration.class, AadB2cOAuth2ClientConfiguration.class}) public class AadB2cAutoConfiguration { + private final RestTemplateBuilder restTemplateBuilder; + + /** + * Creates a new instance of {@link AadOAuth2ClientConfiguration}. + * + * @param restTemplateBuilder the RestTemplateBuilder + */ + public AadB2cAutoConfiguration(RestTemplateBuilder restTemplateBuilder) { + this.restTemplateBuilder = restTemplateBuilder; + } + /** * Declare AADB2CAuthorizationRequestResolver bean. * @param repository The clientRegistrationRepository, @@ -35,7 +56,7 @@ public class AadB2cAutoConfiguration { @Bean @ConditionalOnMissingBean public AadB2cAuthorizationRequestResolver b2cOAuth2AuthorizationRequestResolver( - ClientRegistrationRepository repository, AadB2cProperties properties) { + ClientRegistrationRepository repository, AadB2cProperties properties) { return new AadB2cAuthorizationRequestResolver(repository, properties); } @@ -61,6 +82,18 @@ public AadB2cLogoutSuccessHandler b2cLogoutSuccessHandler(AadB2cProperties prope @ConditionalOnMissingBean public AadB2cOidcLoginConfigurer b2cLoginConfigurer(AadB2cLogoutSuccessHandler handler, AadB2cAuthorizationRequestResolver resolver) { - return new AadB2cOidcLoginConfigurer(handler, resolver); + return new AadB2cOidcLoginConfigurer(handler, resolver, null, restTemplateBuilder); + } + + /** + * Provide {@link JwtDecoderFactory} used in {@link OAuth2LoginConfigurer#init}. The {@link JwtDecoder} created by + * current {@link JwtDecoderFactory} will use {@link RestTemplate} created by {@link RestTemplateBuilder} bean. + * + * @return JwtDecoderFactory + */ + @Bean + @ConditionalOnMissingBean + JwtDecoderFactory azureAdJwtDecoderFactory() { + return new AadB2cOidcIdTokenDecoderFactory(createRestTemplate(restTemplateBuilder)); } } diff --git a/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/aadb2c/AadB2cOidcLoginConfigurer.java b/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/aadb2c/AadB2cOidcLoginConfigurer.java index a6a4bec1d559..3a6b9128e0c9 100644 --- a/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/aadb2c/AadB2cOidcLoginConfigurer.java +++ b/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/aadb2c/AadB2cOidcLoginConfigurer.java @@ -3,6 +3,7 @@ package com.azure.spring.cloud.autoconfigure.aadb2c; import com.azure.spring.cloud.autoconfigure.aadb2c.implementation.AadB2cOAuth2AuthorizationCodeGrantRequestEntityConverter; +import org.springframework.boot.web.client.RestTemplateBuilder; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; import org.springframework.security.oauth2.client.endpoint.DefaultAuthorizationCodeTokenResponseClient; @@ -11,7 +12,7 @@ import org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestResolver; import org.springframework.security.web.authentication.logout.LogoutSuccessHandler; -import java.util.Optional; +import static com.azure.spring.cloud.autoconfigure.aad.implementation.AadRestTemplateCreator.createOAuth2AccessTokenResponseClientRestTemplate; /** * Configure B2C OAUTH2 login properties. @@ -26,6 +27,8 @@ public class AadB2cOidcLoginConfigurer extends AbstractHttpConfigurer accessTokenResponseClient; + private final RestTemplateBuilder restTemplateBuilder; + /** * Creates a new instance of {@link AadB2cOidcLoginConfigurer}. * @@ -33,19 +36,39 @@ public class AadB2cOidcLoginConfigurer extends AbstractHttpConfigurer accessTokenResponseClient) { + this(handler, resolver, accessTokenResponseClient, null); } + /** * Creates a new instance of {@link AadB2cOidcLoginConfigurer}. * * @param handler the AAD B2C logout success handler * @param resolver the AAD B2C authorization request resolver * @param accessTokenResponseClient the AAD B2C access token response client + * @param restTemplateBuilder the RestTemplateBuilder used to build OAuth2AccessTokenResponseClient. + * It will be used only when accessTokenResponseClient is null. */ - public AadB2cOidcLoginConfigurer(LogoutSuccessHandler handler, OAuth2AuthorizationRequestResolver resolver, OAuth2AccessTokenResponseClient accessTokenResponseClient) { + public AadB2cOidcLoginConfigurer(LogoutSuccessHandler handler, + OAuth2AuthorizationRequestResolver resolver, + OAuth2AccessTokenResponseClient accessTokenResponseClient, + RestTemplateBuilder restTemplateBuilder) { this.handler = handler; this.resolver = resolver; this.accessTokenResponseClient = accessTokenResponseClient; + this.restTemplateBuilder = restTemplateBuilder; } /** @@ -60,12 +83,12 @@ public void init(HttpSecurity http) throws Exception { http.logout() .logoutSuccessHandler(handler) .and() - .oauth2Login() + .oauth2Login() .authorizationEndpoint() - .authorizationRequestResolver(resolver) - .and() + .authorizationRequestResolver(resolver) + .and() .tokenEndpoint() - .accessTokenResponseClient(Optional.ofNullable(accessTokenResponseClient).orElseGet(this::accessTokenResponseClient)); + .accessTokenResponseClient(accessTokenResponseClient()); // @formatter:on } @@ -75,8 +98,12 @@ public void init(HttpSecurity http) throws Exception { * @return the access token response client */ protected OAuth2AccessTokenResponseClient accessTokenResponseClient() { - DefaultAuthorizationCodeTokenResponseClient result = new DefaultAuthorizationCodeTokenResponseClient(); - result.setRequestEntityConverter(new AadB2cOAuth2AuthorizationCodeGrantRequestEntityConverter()); - return result; + if (accessTokenResponseClient != null) { + return accessTokenResponseClient; + } + DefaultAuthorizationCodeTokenResponseClient client = new DefaultAuthorizationCodeTokenResponseClient(); + client.setRequestEntityConverter(new AadB2cOAuth2AuthorizationCodeGrantRequestEntityConverter()); + client.setRestOperations(createOAuth2AccessTokenResponseClientRestTemplate(restTemplateBuilder)); + return client; } } diff --git a/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/aadb2c/implementation/AadB2cOidcIdTokenDecoderFactory.java b/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/aadb2c/implementation/AadB2cOidcIdTokenDecoderFactory.java new file mode 100644 index 000000000000..33c545132b65 --- /dev/null +++ b/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/aadb2c/implementation/AadB2cOidcIdTokenDecoderFactory.java @@ -0,0 +1,50 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.spring.cloud.autoconfigure.aadb2c.implementation; + +import org.springframework.security.oauth2.client.registration.ClientRegistration; +import org.springframework.security.oauth2.core.OAuth2AuthenticationException; +import org.springframework.security.oauth2.core.OAuth2Error; +import org.springframework.security.oauth2.core.oidc.OidcIdToken; +import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm; +import org.springframework.security.oauth2.jwt.JwtDecoder; +import org.springframework.security.oauth2.jwt.JwtDecoderFactory; +import org.springframework.security.oauth2.jwt.NimbusJwtDecoder; +import org.springframework.util.StringUtils; +import org.springframework.web.client.RestOperations; + +/** + * A factory that provides a {@link JwtDecoder} used for {@link OidcIdToken} signature verification. + * + */ +public class AadB2cOidcIdTokenDecoderFactory implements JwtDecoderFactory { + + private final RestOperations restOperations; + + /** + * + * @param restOperations The RestOperations used to retrieve jwk from jwkSetUri. + */ + public AadB2cOidcIdTokenDecoderFactory(RestOperations restOperations) { + this.restOperations = restOperations; + } + + @Override + public JwtDecoder createDecoder(ClientRegistration clientRegistration) { + String jwkSetUri = clientRegistration.getProviderDetails().getJwkSetUri(); + if (!StringUtils.hasText(jwkSetUri)) { + OAuth2Error oauth2Error = new OAuth2Error("missing_signature_verifier", + "Failed to find a Signature Verifier for Client Registration: '" + + clientRegistration.getRegistrationId() + + "'. Check to ensure you have configured the JwkSet URI.", + null); + throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString()); + } + return NimbusJwtDecoder + .withJwkSetUri(jwkSetUri) + .jwsAlgorithm(SignatureAlgorithm.RS256) + .restOperations(restOperations) + .build(); + } +} diff --git a/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/aad/RestTemplateTestUtil.java b/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/aad/RestTemplateTestUtil.java new file mode 100644 index 000000000000..3d9e7a84b290 --- /dev/null +++ b/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/aad/RestTemplateTestUtil.java @@ -0,0 +1,134 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.spring.cloud.autoconfigure.aad; + +import com.azure.spring.cloud.autoconfigure.aad.implementation.webapp.AadAzureDelegatedOAuth2AuthorizedClientProvider; +import com.nimbusds.jose.jwk.source.RemoteJWKSet; +import com.nimbusds.jose.proc.JWSVerificationKeySelector; +import com.nimbusds.jose.util.ResourceRetriever; +import com.nimbusds.jwt.proc.DefaultJWTProcessor; +import org.springframework.context.ApplicationContext; +import org.springframework.security.authentication.ProviderManager; +import org.springframework.security.oauth2.client.AuthorizationCodeOAuth2AuthorizedClientProvider; +import org.springframework.security.oauth2.client.DelegatingOAuth2AuthorizedClientProvider; +import org.springframework.security.oauth2.client.OAuth2AuthorizedClientProvider; +import org.springframework.security.oauth2.client.authentication.OAuth2AuthorizationCodeAuthenticationProvider; +import org.springframework.security.oauth2.client.authentication.OAuth2LoginAuthenticationProvider; +import org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient; +import org.springframework.security.oauth2.client.registration.ClientRegistration; +import org.springframework.security.oauth2.client.web.DefaultOAuth2AuthorizedClientManager; +import org.springframework.security.oauth2.client.web.OAuth2LoginAuthenticationFilter; +import org.springframework.security.oauth2.core.AuthorizationGrantType; +import org.springframework.security.oauth2.core.ClientAuthenticationMethod; +import org.springframework.security.oauth2.jwt.JwtDecoder; +import org.springframework.security.oauth2.jwt.JwtDecoderFactory; +import org.springframework.security.oauth2.jwt.NimbusJwtDecoder; +import org.springframework.security.web.FilterChainProxy; +import org.springframework.web.client.RestTemplate; + +import java.util.List; +import java.util.stream.Collectors; + +import static com.azure.spring.cloud.autoconfigure.aad.implementation.RestTemplateProxyCustomizerConfiguration.FACTORY; +import static com.azure.spring.cloud.core.implementation.util.ReflectionUtils.getField; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public final class RestTemplateTestUtil { + + private RestTemplateTestUtil() { + } + + public static void assertRestTemplateWellConfigured(ApplicationContext context) { + assertRestTemplateWellConfiguredForOAuth2AuthorizationCodeAuthenticationProvider(context); + assertRestTemplateWellConfiguredForAllOAuth2AuthorizedClientProviders(context); + assertRestTemplateWellConfiguredForJwtDecoderFactory(context); + } + + private static void assertRestTemplateWellConfiguredForOAuth2AuthorizationCodeAuthenticationProvider(ApplicationContext context) { + OAuth2AuthorizationCodeAuthenticationProvider provider = getOAuth2AuthorizationCodeAuthenticationProvider(context); + OAuth2AccessTokenResponseClient client = (OAuth2AccessTokenResponseClient) getField(provider.getClass(), "accessTokenResponseClient", provider); + assertRestTemplateWellConfiguredInOAuth2AccessTokenResponseClient(client); + } + + private static OAuth2AuthorizationCodeAuthenticationProvider getOAuth2AuthorizationCodeAuthenticationProvider(ApplicationContext context) { + FilterChainProxy proxy = context.getBean(FilterChainProxy.class); + OAuth2LoginAuthenticationFilter filter = proxy.getFilterChains() + .get(0) + .getFilters() + .stream() + .filter(f -> f instanceof OAuth2LoginAuthenticationFilter) + .map(f -> (OAuth2LoginAuthenticationFilter) f) + .findAny() + .orElse(null); + ProviderManager manager = (ProviderManager) getField(OAuth2LoginAuthenticationFilter.class, "authenticationManager", filter); + OAuth2LoginAuthenticationProvider provider = manager.getProviders() + .stream() + .filter(p -> p instanceof OAuth2LoginAuthenticationProvider) + .map(p -> (OAuth2LoginAuthenticationProvider) p) + .findAny() + .orElse(null); + return (OAuth2AuthorizationCodeAuthenticationProvider) getField(OAuth2LoginAuthenticationProvider.class, "authorizationCodeAuthenticationProvider", provider); + } + + private static void assertRestTemplateWellConfiguredForAllOAuth2AuthorizedClientProviders(ApplicationContext context) { + List providers = getAllOAuth2AuthorizedClientProviderThatShouldConfiguredRestTemplate(context); + // 3 providers: refreshToken, clientCredential, password. + assertTrue(providers.size() >= 3); + providers.forEach(provider -> { + OAuth2AccessTokenResponseClient client = (OAuth2AccessTokenResponseClient) getField(provider.getClass(), "accessTokenResponseClient", provider); + assertRestTemplateWellConfiguredInOAuth2AccessTokenResponseClient(client); + }); + } + + @SuppressWarnings("unchecked") + private static List getAllOAuth2AuthorizedClientProviderThatShouldConfiguredRestTemplate(ApplicationContext context) { + final DefaultOAuth2AuthorizedClientManager manager = context.getBean(DefaultOAuth2AuthorizedClientManager.class); + DelegatingOAuth2AuthorizedClientProvider delegatingProvider = + (DelegatingOAuth2AuthorizedClientProvider) getField(DefaultOAuth2AuthorizedClientManager.class, "authorizedClientProvider", manager); + List providers = + (List) getField(DelegatingOAuth2AuthorizedClientProvider.class, "authorizedClientProviders", delegatingProvider); + return providers.stream() + .filter(provider -> !(provider instanceof AuthorizationCodeOAuth2AuthorizedClientProvider)) + .filter(provider -> !(provider instanceof AadAzureDelegatedOAuth2AuthorizedClientProvider)) + .collect(Collectors.toList()); + } + + private static void assertRestTemplateWellConfiguredInOAuth2AccessTokenResponseClient(OAuth2AccessTokenResponseClient client) { + RestTemplate restTemplate = (RestTemplate) getField(client.getClass(), "restOperations", client); + assertEquals(FACTORY, restTemplate.getRequestFactory()); + } + + @SuppressWarnings("unchecked") + private static void assertRestTemplateWellConfiguredForJwtDecoderFactory(ApplicationContext context) { + JwtDecoderFactory factory = (JwtDecoderFactory) context.getBean(JwtDecoderFactory.class); + JwtDecoder jwtDecoder = factory.createDecoder(clientRegistration()); + assertTrue(jwtDecoder instanceof NimbusJwtDecoder); + DefaultJWTProcessor processor = (DefaultJWTProcessor) getField(NimbusJwtDecoder.class, "jwtProcessor", jwtDecoder); + JWSVerificationKeySelector selector = (JWSVerificationKeySelector) processor.getJWSKeySelector(); + RemoteJWKSet source = (RemoteJWKSet) selector.getJWKSource(); + ResourceRetriever retriever = source.getResourceRetriever(); + RestTemplate restTemplate = (RestTemplate) getField(retriever.getClass(), "restOperations", retriever); + assertEquals(FACTORY, restTemplate.getRequestFactory()); + } + + private static ClientRegistration clientRegistration() { + return ClientRegistration + .withRegistrationId("registration-id-1") + .redirectUri("{baseUrl}/{action}/oauth2/code/{registrationId}") + .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC) + .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE) + .scope("scope-1") + .authorizationUri("https://example1.com/login/oauth/authorize") + .tokenUri("https://example1.com/login/oauth/access_token") + .jwkSetUri("https://example1.com/oauth2/jwk") + .issuerUri("https://example1.com") + .userInfoUri("https://api.example1.com/user") + .userNameAttributeName("id-1") + .clientName("Client Name 1") + .clientId("client-id-1") + .clientSecret("client-secret-1") + .build(); + } +} diff --git a/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/aadb2c/implementation/AadB2cAutoConfigurationTests.java b/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/aadb2c/implementation/AadB2cAutoConfigurationTests.java index 2852a9e1f52f..2297c4a4f43e 100644 --- a/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/aadb2c/implementation/AadB2cAutoConfigurationTests.java +++ b/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/aadb2c/implementation/AadB2cAutoConfigurationTests.java @@ -2,9 +2,12 @@ // Licensed under the MIT License. package com.azure.spring.cloud.autoconfigure.aadb2c.implementation; +import com.azure.spring.cloud.autoconfigure.aad.RestTemplateTestUtil; +import com.azure.spring.cloud.autoconfigure.aad.implementation.RestTemplateProxyCustomizerConfiguration; import com.azure.spring.cloud.autoconfigure.aadb2c.AadB2cAuthorizationRequestResolver; import com.azure.spring.cloud.autoconfigure.aadb2c.AadB2cAutoConfiguration; import com.azure.spring.cloud.autoconfigure.aadb2c.AadB2cLogoutSuccessHandler; +import com.azure.spring.cloud.autoconfigure.aadb2c.AadB2cOidcLoginConfigurer; import com.azure.spring.cloud.autoconfigure.aadb2c.AadB2cResourceServerAutoConfiguration; import com.azure.spring.cloud.autoconfigure.aadb2c.properties.AadB2cProperties; import com.azure.spring.cloud.autoconfigure.aadb2c.properties.AuthorizationClientProperties; @@ -19,6 +22,9 @@ import org.springframework.boot.autoconfigure.web.client.RestTemplateAutoConfiguration; import org.springframework.boot.test.context.FilteredClassLoader; import org.springframework.boot.test.context.runner.WebApplicationContextRunner; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.oauth2.core.AuthorizationGrantType; import org.springframework.security.oauth2.server.resource.BearerTokenAuthenticationToken; @@ -249,9 +255,9 @@ void testWebappConditionsIsNotInvokedWhenAADB2CDisabled() { AadB2cConditions.ClientRegistrationCondition clientRegistrationCondition = spy(AadB2cConditions.ClientRegistrationCondition.class); beanUtils.when(() -> BeanUtils.instantiateClass(AadB2cConditions.UserFlowCondition.class)) - .thenReturn(userFlowCondition); + .thenReturn(userFlowCondition); beanUtils.when(() -> BeanUtils.instantiateClass(AadB2cConditions.ClientRegistrationCondition.class)) - .thenReturn(clientRegistrationCondition); + .thenReturn(clientRegistrationCondition); new WebApplicationContextRunner() .withConfiguration(AutoConfigurations.of( WebResourceServerApp.class, @@ -266,4 +272,33 @@ void testWebappConditionsIsNotInvokedWhenAADB2CDisabled() { }); } } + + @Test + void testRestTemplateWellConfigured() { + getDefaultContextRunner() + .withUserConfiguration(RestTemplateProxyCustomizerConfiguration.class, AadB2cTestWebSecurityConfiguration.class) + .withPropertyValues("spring.cloud.azure.active-directory.b2c.enabled=true") + .run(RestTemplateTestUtil::assertRestTemplateWellConfigured); + } + + @EnableWebSecurity + public static class AadB2cTestWebSecurityConfiguration extends WebSecurityConfigurerAdapter { + + private final AadB2cOidcLoginConfigurer configurer; + + AadB2cTestWebSecurityConfiguration(AadB2cOidcLoginConfigurer configurer) { + this.configurer = configurer; + } + + @Override + protected void configure(HttpSecurity http) throws Exception { + // @formatter:off + http + .authorizeRequests() + .anyRequest().authenticated() + .and() + .apply(configurer); + // @formatter:on + } + } } From 4302547e79f8506df436074dc7f5ccb7a1dcf229 Mon Sep 17 00:00:00 2001 From: Rujun Chen Date: Mon, 24 Oct 2022 09:18:10 +0800 Subject: [PATCH 7/8] Fix pipeline failure caused by maven-checkstyle-plugin. --- .../AadOAuth2ClientConfigurationTests.java | 268 +++++++++--------- 1 file changed, 134 insertions(+), 134 deletions(-) diff --git a/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/aad/implementation/oauth2/AadOAuth2ClientConfigurationTests.java b/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/aad/implementation/oauth2/AadOAuth2ClientConfigurationTests.java index 831847bace3e..b98b842c636d 100644 --- a/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/aad/implementation/oauth2/AadOAuth2ClientConfigurationTests.java +++ b/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/aad/implementation/oauth2/AadOAuth2ClientConfigurationTests.java @@ -52,144 +52,144 @@ class AadOAuth2ClientConfigurationTests { @Test void testWithoutAnyPropertiesSet() { new WebApplicationContextRunner() - .withConfiguration(AutoConfigurations.of( - HttpMessageConvertersAutoConfiguration.class, - RestTemplateAutoConfiguration.class)) - .withUserConfiguration(AadOAuth2ClientConfiguration.class) - .run(context -> { - assertThat(context).doesNotHaveBean(AadAuthenticationProperties.class); - assertThat(context).doesNotHaveBean(ClientRegistrationRepository.class); - assertThat(context).doesNotHaveBean(OAuth2AuthorizedClientRepository.class); - }); + .withConfiguration(AutoConfigurations.of( + HttpMessageConvertersAutoConfiguration.class, + RestTemplateAutoConfiguration.class)) + .withUserConfiguration(AadOAuth2ClientConfiguration.class) + .run(context -> { + assertThat(context).doesNotHaveBean(AadAuthenticationProperties.class); + assertThat(context).doesNotHaveBean(ClientRegistrationRepository.class); + assertThat(context).doesNotHaveBean(OAuth2AuthorizedClientRepository.class); + }); } @Test void testWithRequiredPropertiesSet() { oauthClientAndResourceServerRunner() - .withPropertyValues( - "spring.cloud.azure.active-directory.enabled=true", - "spring.cloud.azure.active-directory.credential.client-id=fake-client-id" - ) - .run(context -> { - assertThat(context).hasSingleBean(AadAuthenticationProperties.class); - assertThat(context).hasSingleBean(ClientRegistrationRepository.class); - assertThat(context).hasSingleBean(OAuth2AuthorizedClientRepository.class); - }); + .withPropertyValues( + "spring.cloud.azure.active-directory.enabled=true", + "spring.cloud.azure.active-directory.credential.client-id=fake-client-id" + ) + .run(context -> { + assertThat(context).hasSingleBean(AadAuthenticationProperties.class); + assertThat(context).hasSingleBean(ClientRegistrationRepository.class); + assertThat(context).hasSingleBean(OAuth2AuthorizedClientRepository.class); + }); } @Test void testWebApplication() { webApplicationContextRunner() - .withPropertyValues( - "spring.cloud.azure.active-directory.enabled=true" - ) - .run(context -> { - assertThat(context).hasSingleBean(AadAuthenticationProperties.class); - assertThat(context).hasSingleBean(ClientRegistrationRepository.class); - assertThat(context).hasSingleBean(OAuth2AuthorizedClientRepository.class); - }); + .withPropertyValues( + "spring.cloud.azure.active-directory.enabled=true" + ) + .run(context -> { + assertThat(context).hasSingleBean(AadAuthenticationProperties.class); + assertThat(context).hasSingleBean(ClientRegistrationRepository.class); + assertThat(context).hasSingleBean(OAuth2AuthorizedClientRepository.class); + }); } @Test void testResourceServer() { resourceServerContextRunner() - .run(context -> assertThat(context).doesNotHaveBean(OAuth2AuthorizedClientRepository.class)); + .run(context -> assertThat(context).doesNotHaveBean(OAuth2AuthorizedClientRepository.class)); } @Test void testResourceServerWithOboOnlyGraphClient() { resourceServerWithOboContextRunner() - .withPropertyValues( - "spring.cloud.azure.active-directory.enabled=true", - "spring.cloud.azure.active-directory.authorization-clients.graph.scopes=https://graph.microsoft.com/User.Read") - .run(context -> { - final AadClientRegistrationRepository oboRepo = context.getBean( - AadClientRegistrationRepository.class); - final OAuth2AuthorizedClientRepository aadOboRepo = context.getBean( - OAuth2AuthorizedClientRepository.class); + .withPropertyValues( + "spring.cloud.azure.active-directory.enabled=true", + "spring.cloud.azure.active-directory.authorization-clients.graph.scopes=https://graph.microsoft.com/User.Read") + .run(context -> { + final AadClientRegistrationRepository oboRepo = context.getBean( + AadClientRegistrationRepository.class); + final OAuth2AuthorizedClientRepository aadOboRepo = context.getBean( + OAuth2AuthorizedClientRepository.class); - ClientRegistration graph = oboRepo.findByRegistrationId("graph"); - Set graphScopes = graph.getScopes(); + ClientRegistration graph = oboRepo.findByRegistrationId("graph"); + Set graphScopes = graph.getScopes(); - assertThat(aadOboRepo).isNotNull(); - assertThat(oboRepo).isExactlyInstanceOf(AadClientRegistrationRepository.class); - assertThat(graph).isNotNull(); - assertThat(graphScopes).containsOnly("https://graph.microsoft.com/User.Read"); - }); + assertThat(aadOboRepo).isNotNull(); + assertThat(oboRepo).isExactlyInstanceOf(AadClientRegistrationRepository.class); + assertThat(graph).isNotNull(); + assertThat(graphScopes).containsOnly("https://graph.microsoft.com/User.Read"); + }); } @Test void testResourceServerWithOboInvalidGrantType1() { resourceServerWithOboContextRunner() - .withPropertyValues( - "spring.cloud.azure.active-directory.authorization-clients.graph.authorization-grant-type=authorization_code", - "spring.cloud.azure.active-directory.enabled=true" - ) - .run(context -> - assertThrows(IllegalStateException.class, () -> context.getBean(AadAuthenticationProperties.class)) - ); + .withPropertyValues( + "spring.cloud.azure.active-directory.authorization-clients.graph.authorization-grant-type=authorization_code", + "spring.cloud.azure.active-directory.enabled=true" + ) + .run(context -> + assertThrows(IllegalStateException.class, () -> context.getBean(AadAuthenticationProperties.class)) + ); } @Test void testResourceServerWithOboInvalidGrantType2() { resourceServerWithOboContextRunner() - .withPropertyValues( - "spring.cloud.azure.active-directory.enabled=true", - "spring.cloud.azure.active-directory.authorization-clients.graph.authorization-grant-type=on_behalf_of", - "spring.cloud.azure.active-directory.authorization-clients.graph.authorization-grant-type = authorization_code" - ) - .run(context -> - assertThrows(IllegalStateException.class, () -> context.getBean(AadAuthenticationProperties.class)) - ); + .withPropertyValues( + "spring.cloud.azure.active-directory.enabled=true", + "spring.cloud.azure.active-directory.authorization-clients.graph.authorization-grant-type=on_behalf_of", + "spring.cloud.azure.active-directory.authorization-clients.graph.authorization-grant-type = authorization_code" + ) + .run(context -> + assertThrows(IllegalStateException.class, () -> context.getBean(AadAuthenticationProperties.class)) + ); } @Test void testResourceServerWithOboExistCustomAndGraphClient() { resourceServerWithOboContextRunner() - .withPropertyValues( - "spring.cloud.azure.active-directory.enabled=true", - "spring.cloud.azure.active-directory.authorization-clients.graph.scopes=https://graph.microsoft.com/User.Read", - "spring.cloud.azure.active-directory.authorization-clients.custom.scopes=api://52261059-e515-488e-84fd-a09a3f372814/File.Read" - ) - .run(context -> { - final AadClientRegistrationRepository oboRepo = context.getBean( - AadClientRegistrationRepository.class); - final OAuth2AuthorizedClientRepository aadOboRepo = context.getBean( - OAuth2AuthorizedClientRepository.class); + .withPropertyValues( + "spring.cloud.azure.active-directory.enabled=true", + "spring.cloud.azure.active-directory.authorization-clients.graph.scopes=https://graph.microsoft.com/User.Read", + "spring.cloud.azure.active-directory.authorization-clients.custom.scopes=api://52261059-e515-488e-84fd-a09a3f372814/File.Read" + ) + .run(context -> { + final AadClientRegistrationRepository oboRepo = context.getBean( + AadClientRegistrationRepository.class); + final OAuth2AuthorizedClientRepository aadOboRepo = context.getBean( + OAuth2AuthorizedClientRepository.class); - ClientRegistration graph = oboRepo.findByRegistrationId("graph"); - ClientRegistration custom = oboRepo.findByRegistrationId("custom"); - Set graphScopes = graph.getScopes(); - Set customScopes = custom.getScopes(); + ClientRegistration graph = oboRepo.findByRegistrationId("graph"); + ClientRegistration custom = oboRepo.findByRegistrationId("custom"); + Set graphScopes = graph.getScopes(); + Set customScopes = custom.getScopes(); - assertThat(aadOboRepo).isNotNull(); - assertThat(oboRepo).isExactlyInstanceOf(AadClientRegistrationRepository.class); - assertThat(graph).isNotNull(); - assertThat(customScopes).isNotNull(); - assertThat(graphScopes).containsOnly("https://graph.microsoft.com/User.Read"); - assertThat(customScopes).containsOnly("api://52261059-e515-488e-84fd-a09a3f372814/File.Read"); - }); + assertThat(aadOboRepo).isNotNull(); + assertThat(oboRepo).isExactlyInstanceOf(AadClientRegistrationRepository.class); + assertThat(graph).isNotNull(); + assertThat(customScopes).isNotNull(); + assertThat(graphScopes).containsOnly("https://graph.microsoft.com/User.Read"); + assertThat(customScopes).containsOnly("api://52261059-e515-488e-84fd-a09a3f372814/File.Read"); + }); } @Test void defaultConverterInJwtBearerOAuth2AuthorizedClientProviderWhenNotUsingPrivateKeyJwtMethod() { resourceServerWithOboContextRunner() - .withUserConfiguration(AadOAuth2ClientConfiguration.class) - .withPropertyValues( - "spring.cloud.azure.active-directory.enabled=true", - "spring.cloud.azure.active-directory.authorization-clients.graph.scopes=https://graph.microsoft.com/User.Read", - "spring.cloud.azure.active-directory.authorization-clients.graph.authorization-grant-type=on_behalf_of", - "spring.cloud.azure.active-directory.authorization-clients.graph.scopes=api://52261059-e515-488e-84fd-a09a3f372814/File.Read" - ) - .run(context -> { - assertThat(context).doesNotHaveBean(OAuth2ClientAuthenticationJwkResolver.class); - final JwtBearerOAuth2AuthorizedClientProvider jwtBearerProvider = context.getBean( - JwtBearerOAuth2AuthorizedClientProvider.class); - final ClientRegistrationRepository clientRepository = context.getBean( - ClientRegistrationRepository.class); - MultiValueMap parameters = convertParameters(jwtBearerProvider, clientRepository); - assertThat(parameters).containsEntry("requested_token_use", Arrays.asList("on_behalf_of")); - }); + .withUserConfiguration(AadOAuth2ClientConfiguration.class) + .withPropertyValues( + "spring.cloud.azure.active-directory.enabled=true", + "spring.cloud.azure.active-directory.authorization-clients.graph.scopes=https://graph.microsoft.com/User.Read", + "spring.cloud.azure.active-directory.authorization-clients.graph.authorization-grant-type=on_behalf_of", + "spring.cloud.azure.active-directory.authorization-clients.graph.scopes=api://52261059-e515-488e-84fd-a09a3f372814/File.Read" + ) + .run(context -> { + assertThat(context).doesNotHaveBean(OAuth2ClientAuthenticationJwkResolver.class); + final JwtBearerOAuth2AuthorizedClientProvider jwtBearerProvider = context.getBean( + JwtBearerOAuth2AuthorizedClientProvider.class); + final ClientRegistrationRepository clientRepository = context.getBean( + ClientRegistrationRepository.class); + MultiValueMap parameters = convertParameters(jwtBearerProvider, clientRepository); + assertThat(parameters).containsEntry("requested_token_use", Arrays.asList("on_behalf_of")); + }); } @Test @@ -200,57 +200,57 @@ void customConverterInJwtBearerOAuth2AuthorizedClientProviderWhenUsingPrivateKey given(rsaJwk.getX509CertThumbprint()).willReturn(new Base64URL("dGVzdA")); resourceServerWithOboContextRunner() - .withBean(OAuth2ClientAuthenticationJwkResolver.class, () -> jwkResolver) - .withUserConfiguration(AadOAuth2ClientConfiguration.class) - .withPropertyValues( - "spring.cloud.azure.active-directory.enabled=true", - "spring.cloud.azure.active-directory.credential.client-certificate-path=/test/test.pfx", - "spring.cloud.azure.active-directory.credential.client-certificate-password=test", - "spring.cloud.azure.active-directory.authorization-clients.graph.scopes=https://graph.microsoft.com/User.Read", - "spring.cloud.azure.active-directory.authorization-clients.graph.authorization-grant-type=urn:ietf:params:oauth:grant-type:jwt-bearer", - "spring.cloud.azure.active-directory.authorization-clients.graph.scopes=api://52261059-e515-488e-84fd-a09a3f372814/File.Read", - "spring.cloud.azure.active-directory.authorization-clients.graph.client-authentication-method=private_key_jwt" - ) - .run(context -> { - assertThat(context).hasSingleBean(OAuth2ClientAuthenticationJwkResolver.class); - final JwtBearerOAuth2AuthorizedClientProvider jwtBearerProvider = context.getBean( - JwtBearerOAuth2AuthorizedClientProvider.class); - final ClientRegistrationRepository clientRepository = context.getBean( - ClientRegistrationRepository.class); + .withBean(OAuth2ClientAuthenticationJwkResolver.class, () -> jwkResolver) + .withUserConfiguration(AadOAuth2ClientConfiguration.class) + .withPropertyValues( + "spring.cloud.azure.active-directory.enabled=true", + "spring.cloud.azure.active-directory.credential.client-certificate-path=/test/test.pfx", + "spring.cloud.azure.active-directory.credential.client-certificate-password=test", + "spring.cloud.azure.active-directory.authorization-clients.graph.scopes=https://graph.microsoft.com/User.Read", + "spring.cloud.azure.active-directory.authorization-clients.graph.authorization-grant-type=urn:ietf:params:oauth:grant-type:jwt-bearer", + "spring.cloud.azure.active-directory.authorization-clients.graph.scopes=api://52261059-e515-488e-84fd-a09a3f372814/File.Read", + "spring.cloud.azure.active-directory.authorization-clients.graph.client-authentication-method=private_key_jwt" + ) + .run(context -> { + assertThat(context).hasSingleBean(OAuth2ClientAuthenticationJwkResolver.class); + final JwtBearerOAuth2AuthorizedClientProvider jwtBearerProvider = context.getBean( + JwtBearerOAuth2AuthorizedClientProvider.class); + final ClientRegistrationRepository clientRepository = context.getBean( + ClientRegistrationRepository.class); - MultiValueMap parameters = convertParameters(jwtBearerProvider, clientRepository); - assertThat(parameters).containsEntry("requested_token_use", Arrays.asList("on_behalf_of")); - assertThat(parameters).containsKey(OAuth2ParameterNames.CLIENT_ASSERTION_TYPE); - verify(jwkResolver).resolve(clientRepository.findByRegistrationId("graph")); - }); + MultiValueMap parameters = convertParameters(jwtBearerProvider, clientRepository); + assertThat(parameters).containsEntry("requested_token_use", Arrays.asList("on_behalf_of")); + assertThat(parameters).containsKey(OAuth2ParameterNames.CLIENT_ASSERTION_TYPE); + verify(jwkResolver).resolve(clientRepository.findByRegistrationId("graph")); + }); } @Test void restTemplateWellConfiguredWhenNotUsingPrivateKeyJwtMethod() { webApplicationContextRunner() - .withUserConfiguration(AadOAuth2ClientConfiguration.class, RestTemplateProxyCustomizerConfiguration.class) - .run(context -> { - assertThat(context).doesNotHaveBean(OAuth2ClientAuthenticationJwkResolver.class); - assertRestTemplateWellConfigured(context); - }); + .withUserConfiguration(AadOAuth2ClientConfiguration.class, RestTemplateProxyCustomizerConfiguration.class) + .run(context -> { + assertThat(context).doesNotHaveBean(OAuth2ClientAuthenticationJwkResolver.class); + assertRestTemplateWellConfigured(context); + }); } @Test void restTemplateWellConfiguredWhenUsingPrivateKeyJwtMethod() { webApplicationContextRunner() - .withPropertyValues( - "spring.cloud.azure.active-directory.enabled=true", - "spring.cloud.azure.active-directory.credential.client-certificate-path=/test/test.pfx", - "spring.cloud.azure.active-directory.credential.client-certificate-password=test", - "spring.cloud.azure.active-directory.authorization-clients.graph.client-authentication-method=private_key_jwt", - "spring.cloud.azure.active-directory.authorization-clients.graph.scopes=https://graph.microsoft.com/User.Read", - "spring.cloud.azure.active-directory.authorization-clients.graph.scopes=api://52261059-e515-488e-84fd-a09a3f372814/File.Read" - ) - .withUserConfiguration(AadOAuth2ClientConfiguration.class, RestTemplateProxyCustomizerConfiguration.class) - .run(context -> { - assertThat(context).hasSingleBean(OAuth2ClientAuthenticationJwkResolver.class); - assertRestTemplateWellConfigured(context); - }); + .withPropertyValues( + "spring.cloud.azure.active-directory.enabled=true", + "spring.cloud.azure.active-directory.credential.client-certificate-path=/test/test.pfx", + "spring.cloud.azure.active-directory.credential.client-certificate-password=test", + "spring.cloud.azure.active-directory.authorization-clients.graph.client-authentication-method=private_key_jwt", + "spring.cloud.azure.active-directory.authorization-clients.graph.scopes=https://graph.microsoft.com/User.Read", + "spring.cloud.azure.active-directory.authorization-clients.graph.scopes=api://52261059-e515-488e-84fd-a09a3f372814/File.Read" + ) + .withUserConfiguration(AadOAuth2ClientConfiguration.class, RestTemplateProxyCustomizerConfiguration.class) + .run(context -> { + assertThat(context).hasSingleBean(OAuth2ClientAuthenticationJwkResolver.class); + assertRestTemplateWellConfigured(context); + }); } @SuppressWarnings("unchecked") From c8dab5635ce0859c2b8e77e650658dee81a7af2c Mon Sep 17 00:00:00 2001 From: Rujun Chen Date: Mon, 24 Oct 2022 09:38:56 +0800 Subject: [PATCH 8/8] Change version from "4.4.0" to "4.4.1". --- sdk/spring/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/spring/CHANGELOG.md b/sdk/spring/CHANGELOG.md index 41971dd4f8be..9bb2895aa2b3 100644 --- a/sdk/spring/CHANGELOG.md +++ b/sdk/spring/CHANGELOG.md @@ -1,6 +1,6 @@ # Release History -## 4.4.0 (Unreleased) +## 4.4.1 (Unreleased) #### Bugs Fixed - Fix bug: Put a value into Collections.emptyMap(). [#31190](https://github.com/Azure/azure-sdk-for-java/issues/31190).