diff --git a/sdk/spring/CHANGELOG.md b/sdk/spring/CHANGELOG.md index ef1c71526c0d..ce7ebf3302bc 100644 --- a/sdk/spring/CHANGELOG.md +++ b/sdk/spring/CHANGELOG.md @@ -9,6 +9,7 @@ This section includes changes in `spring-cloud-azure-autoconfigure` module. #### Bugs Fixed - Fix the issue that `AzureMessagingListenerAutoConfiguration` not included in spring.factories [#34690](https://github.com/Azure/azure-sdk-for-java/issues/34690), [#35716](https://github.com/Azure/azure-sdk-for-java/pull/35716). +- Fix default AAD configuration could be activated multiple times [#36124](https://github.com/Azure/azure-sdk-for-java/pull/36124). ## 4.9.0 (2023-06-29) - This release is compatible with Spring Boot 2.5.0-2.5.15, 2.6.0-2.6.15, 2.7.0-2.7.11. (Note: 2.5.x (x>15), 2.6.y (y>15) and 2.7.z (z>11) should be supported, but they aren't tested with this release.) 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 d4979f464c1f..b6715fcf49b4 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 @@ -11,6 +11,7 @@ import com.azure.spring.cloud.autoconfigure.aad.properties.AadAuthorizationServerEndpoints; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.security.ConditionalOnDefaultWebSecurity; import org.springframework.boot.web.client.RestTemplateBuilder; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Conditional; @@ -18,7 +19,6 @@ import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; 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.DelegatingOAuth2TokenValidator; import org.springframework.security.oauth2.core.OAuth2TokenValidator; import org.springframework.security.oauth2.jwt.Jwt; @@ -103,7 +103,7 @@ public List> createDefaultValidator(AadAuthenticationP */ @EnableWebSecurity @EnableGlobalMethodSecurity(prePostEnabled = true) - @ConditionalOnMissingBean(WebSecurityConfigurerAdapter.class) + @ConditionalOnDefaultWebSecurity @ConditionalOnExpression("!'${spring.cloud.azure.active-directory.application-type}'.equalsIgnoreCase('web_application_and_resource_server')") public static class DefaultAadResourceServerWebSecurityConfigurerAdapter extends AadResourceServerWebSecurityConfigurerAdapter { diff --git a/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/aad/configuration/AadWebApplicationConfiguration.java b/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/aad/configuration/AadWebApplicationConfiguration.java index 93dac2ac4bd0..f56bd4438355 100644 --- a/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/aad/configuration/AadWebApplicationConfiguration.java +++ b/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/aad/configuration/AadWebApplicationConfiguration.java @@ -9,6 +9,7 @@ import com.azure.spring.cloud.autoconfigure.aad.properties.AadAuthenticationProperties; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.security.ConditionalOnDefaultWebSecurity; import org.springframework.boot.web.client.RestTemplateBuilder; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Conditional; @@ -16,7 +17,6 @@ import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; 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.client.oidc.userinfo.OidcUserRequest; import org.springframework.security.oauth2.client.userinfo.OAuth2UserService; import org.springframework.security.oauth2.core.oidc.user.OidcUser; @@ -57,7 +57,7 @@ public OAuth2UserService oidcUserService(AadAuthentic */ @EnableWebSecurity @EnableGlobalMethodSecurity(prePostEnabled = true) - @ConditionalOnMissingBean(WebSecurityConfigurerAdapter.class) + @ConditionalOnDefaultWebSecurity @ConditionalOnExpression("!'${spring.cloud.azure.active-directory.application-type}'.equalsIgnoreCase('web_application_and_resource_server')") public static class DefaultAadWebSecurityConfigurerAdapter extends AadWebSecurityConfigurerAdapter { diff --git a/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/aad/configuration/AadResourceServerConfigurationTests.java b/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/aad/configuration/AadResourceServerConfigurationTests.java new file mode 100644 index 000000000000..2d2f49f60579 --- /dev/null +++ b/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/aad/configuration/AadResourceServerConfigurationTests.java @@ -0,0 +1,93 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.spring.cloud.autoconfigure.aad.configuration; + +import com.azure.spring.cloud.autoconfigure.aad.AadAutoConfiguration; +import com.azure.spring.cloud.autoconfigure.aad.AadResourceServerWebSecurityConfigurerAdapter; +import com.azure.spring.cloud.autoconfigure.context.AzureGlobalPropertiesAutoConfiguration; +import org.junit.jupiter.api.Test; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration; +import org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener; +import org.springframework.boot.autoconfigure.web.client.RestTemplateAutoConfiguration; +import org.springframework.boot.logging.LogLevel; +import org.springframework.boot.test.context.runner.WebApplicationContextRunner; +import org.springframework.context.annotation.Bean; +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.config.annotation.web.configurers.oauth2.server.resource.OAuth2ResourceServerConfigurer; +import org.springframework.security.web.SecurityFilterChain; + +import static com.azure.spring.cloud.autoconfigure.aad.implementation.WebApplicationContextRunnerUtils.resourceServerRunner; +import static org.assertj.core.api.Assertions.assertThat; + +class AadResourceServerConfigurationTests { + @Test + void useDefaultWebSecurityConfigurerAdapter() { + resourceServerRunner() + .withPropertyValues("spring.cloud.azure.active-directory.enabled=true", + "spring.cloud.azure.active-directory.credential.client-id=fake-client-id" + ) + .run(context -> { + assertThat(context).hasSingleBean(AadResourceServerWebSecurityConfigurerAdapter.class); + assertThat(context).hasSingleBean(AadResourceServerConfiguration.DefaultAadResourceServerWebSecurityConfigurerAdapter.class); + }); + } + + @Test + void useCustomWebSecurityConfigurerAdapter() { + resourceServerRunner() + .withUserConfiguration(TestAadResourceServerWebSecurityConfigurerAdapter.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(WebSecurityConfigurerAdapter.class); + assertThat(context).doesNotHaveBean(AadResourceServerConfiguration.DefaultAadResourceServerWebSecurityConfigurerAdapter.class); + assertThat(context).hasSingleBean(TestAadResourceServerWebSecurityConfigurerAdapter.class); + }); + } + + @Test + void useCustomSecurityFilterChain() { + new WebApplicationContextRunner() + .withConfiguration(AutoConfigurations.of( + HttpMessageConvertersAutoConfiguration.class, + RestTemplateAutoConfiguration.class)) + .withUserConfiguration(AzureGlobalPropertiesAutoConfiguration.class, + TestSecurityFilterChain.class, + AadAutoConfiguration.class) + .withInitializer(new ConditionEvaluationReportLoggingListener(LogLevel.INFO)) + .withPropertyValues("spring.cloud.azure.active-directory.enabled=true", + "spring.cloud.azure.active-directory.credential.client-id=fake-client-id" + ) + .run(context -> { + assertThat(context).hasSingleBean(SecurityFilterChain.class); + assertThat(context).doesNotHaveBean(WebSecurityConfigurerAdapter.class); + assertThat(context).doesNotHaveBean(AadResourceServerConfiguration.DefaultAadResourceServerWebSecurityConfigurerAdapter.class); + assertThat(context).hasBean("testSecurityFilterChain"); + + }); + } + + @EnableWebSecurity + static class TestAadResourceServerWebSecurityConfigurerAdapter extends + AadResourceServerWebSecurityConfigurerAdapter { + + @Override + protected void configure(HttpSecurity http) throws Exception { + super.configure(http); + } + } + + @EnableWebSecurity + static class TestSecurityFilterChain { + + @Bean + public SecurityFilterChain testSecurityFilterChain(HttpSecurity http) throws Exception { + return http.oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt).build(); + } + } +} diff --git a/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/aad/configuration/AadWebApplicationConfigurationTests.java b/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/aad/configuration/AadWebApplicationConfigurationTests.java new file mode 100644 index 000000000000..ec0cbb130f06 --- /dev/null +++ b/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/aad/configuration/AadWebApplicationConfigurationTests.java @@ -0,0 +1,92 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.spring.cloud.autoconfigure.aad.configuration; + +import com.azure.spring.cloud.autoconfigure.aad.AadAutoConfiguration; +import com.azure.spring.cloud.autoconfigure.aad.AadWebSecurityConfigurerAdapter; +import com.azure.spring.cloud.autoconfigure.context.AzureGlobalPropertiesAutoConfiguration; +import org.junit.jupiter.api.Test; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration; +import org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener; +import org.springframework.boot.autoconfigure.web.client.RestTemplateAutoConfiguration; +import org.springframework.boot.logging.LogLevel; +import org.springframework.boot.test.context.runner.WebApplicationContextRunner; +import org.springframework.context.annotation.Bean; +import org.springframework.security.config.Customizer; +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.web.SecurityFilterChain; + +import static com.azure.spring.cloud.autoconfigure.aad.implementation.WebApplicationContextRunnerUtils.webApplicationContextRunner; +import static org.assertj.core.api.Assertions.assertThat; + +class AadWebApplicationConfigurationTests { + @Test + void useDefaultWebSecurityConfigurerAdapter() { + webApplicationContextRunner() + .withPropertyValues("spring.cloud.azure.active-directory.enabled=true", + "spring.cloud.azure.active-directory.credential.client-id=fake-client-id" + ) + .run(context -> { + assertThat(context).hasSingleBean(AadWebSecurityConfigurerAdapter.class); + assertThat(context).hasSingleBean(AadWebApplicationConfiguration.DefaultAadWebSecurityConfigurerAdapter.class); + }); + } + + @Test + void useCustomWebSecurityConfigurerAdapter() { + webApplicationContextRunner() + .withUserConfiguration(TestAadWebSecurityConfigurerAdapter.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(WebSecurityConfigurerAdapter.class); + assertThat(context).doesNotHaveBean(AadWebApplicationConfiguration.DefaultAadWebSecurityConfigurerAdapter.class); + assertThat(context).hasSingleBean(TestAadWebSecurityConfigurerAdapter.class); + }); + } + + @Test + void useCustomSecurityFilterChain() { + new WebApplicationContextRunner() + .withConfiguration(AutoConfigurations.of( + HttpMessageConvertersAutoConfiguration.class, + RestTemplateAutoConfiguration.class)) + .withUserConfiguration(AzureGlobalPropertiesAutoConfiguration.class, + AadResourceServerConfigurationTests.TestSecurityFilterChain.class, + AadAutoConfiguration.class) + .withInitializer(new ConditionEvaluationReportLoggingListener(LogLevel.INFO)) + .withPropertyValues("spring.cloud.azure.active-directory.enabled=true", + "spring.cloud.azure.active-directory.credential.client-id=fake-client-id" + ) + .run(context -> { + assertThat(context).hasSingleBean(SecurityFilterChain.class); + assertThat(context).doesNotHaveBean(WebSecurityConfigurerAdapter.class); + assertThat(context).doesNotHaveBean(AadWebApplicationConfiguration.DefaultAadWebSecurityConfigurerAdapter.class); + assertThat(context).hasBean("testSecurityFilterChain"); + }); + } + + @EnableWebSecurity + static class TestAadWebSecurityConfigurerAdapter extends + AadWebSecurityConfigurerAdapter { + + @Override + protected void configure(HttpSecurity http) throws Exception { + super.configure(http); + } + } + + @EnableWebSecurity + static class TestSecurityFilterChain { + + @Bean + public SecurityFilterChain testSecurityFilterChain(HttpSecurity http) throws Exception { + return http.oauth2Login(Customizer.withDefaults()).authorizeRequests(request -> request.anyRequest().authenticated()).build(); + } + } +}