diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/client/OAuth2RestOperationsConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/client/OAuth2RestOperationsConfiguration.java index 50ea0bc30a..2f93b81cd6 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/client/OAuth2RestOperationsConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/client/OAuth2RestOperationsConfiguration.java @@ -16,16 +16,24 @@ package org.springframework.boot.autoconfigure.security.oauth2.client; +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + import javax.annotation.Resource; import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.boot.autoconfigure.condition.AnyNestedCondition; import org.springframework.boot.autoconfigure.condition.ConditionMessage; import org.springframework.boot.autoconfigure.condition.ConditionOutcome; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnNotWebApplication; -import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.autoconfigure.condition.NoneNestedConditions; import org.springframework.boot.autoconfigure.condition.SpringBootCondition; import org.springframework.boot.autoconfigure.security.SecurityProperties; import org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2RestOperationsConfiguration.OAuth2ClientIdCondition; @@ -68,7 +76,7 @@ import org.springframework.util.StringUtils; public class OAuth2RestOperationsConfiguration { @Configuration - @ConditionalOnNotWebApplication + @ConditionalOnClientCredentials protected static class SingletonScopedConfiguration { @Bean @@ -88,7 +96,7 @@ public class OAuth2RestOperationsConfiguration { @Configuration @ConditionalOnBean(OAuth2ClientConfiguration.class) - @ConditionalOnWebApplication + @ConditionalOnNotClientCredentials @Import(OAuth2ProtectedResourceDetailsConfiguration.class) protected static class SessionScopedConfiguration { @@ -126,7 +134,7 @@ public class OAuth2RestOperationsConfiguration { */ @Configuration @ConditionalOnMissingBean(OAuth2ClientConfiguration.class) - @ConditionalOnWebApplication + @ConditionalOnNotClientCredentials @Import(OAuth2ProtectedResourceDetailsConfiguration.class) protected static class RequestScopedConfiguration { @@ -174,4 +182,47 @@ public class OAuth2RestOperationsConfiguration { } + @Conditional(ClientCredentialsCondition.class) + @Target({ ElementType.TYPE, ElementType.METHOD }) + @Retention(RetentionPolicy.RUNTIME) + @Documented + public static @interface ConditionalOnClientCredentials { + + } + + @Conditional(NotClientCredentialsCondition.class) + @Target({ ElementType.TYPE, ElementType.METHOD }) + @Retention(RetentionPolicy.RUNTIME) + @Documented + public static @interface ConditionalOnNotClientCredentials { + + } + + static class ClientCredentialsCondition extends AnyNestedCondition { + + public ClientCredentialsCondition() { + super(ConfigurationPhase.PARSE_CONFIGURATION); + } + + @ConditionalOnProperty(prefix = "security.oauth2.client", name = "grant-type", havingValue = "client_credentials", matchIfMissing = false) + static class ClientCredentialsConfigured { + } + + @ConditionalOnNotWebApplication + static class NoWebApplication { + } + } + + static class NotClientCredentialsCondition extends NoneNestedConditions { + + public NotClientCredentialsCondition() { + super(ConfigurationPhase.PARSE_CONFIGURATION); + } + + @ConditionalOnClientCredentials + static class ClientCredentialsActivated { + } + + } + } diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/OAuth2AutoConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/OAuth2AutoConfigurationTests.java index f5a75ebdcc..dc32c4d16e 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/OAuth2AutoConfigurationTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/OAuth2AutoConfigurationTests.java @@ -21,6 +21,7 @@ import java.util.Arrays; import java.util.List; import com.fasterxml.jackson.databind.JsonNode; + import org.junit.Test; import org.springframework.aop.support.AopUtils; @@ -186,6 +187,44 @@ public class OAuth2AutoConfigurationTests { assertThat(countBeans(OAuth2ClientContext.class)).isEqualTo(2); } + @Test + public void testCanUseClientCredentials() { + this.context = new AnnotationConfigEmbeddedWebApplicationContext(); + this.context.register(TestSecurityConfiguration.class, + MinimalSecureWebApplication.class); + EnvironmentTestUtils.addEnvironment(this.context, + "security.oauth2.client.clientId=client", + "security.oauth2.client.grantType=client_credentials"); + this.context.refresh(); + assertThat(context.getBean(OAuth2ClientContext.class).getAccessTokenRequest()) + .isNotNull(); + assertThat(countBeans(ClientCredentialsResourceDetails.class)).isEqualTo(1); + assertThat(countBeans(OAuth2ClientContext.class)).isEqualTo(1); + } + + @Test + public void testCanUseClientCredentialsWithEnableOAuth2Client() { + this.context = new AnnotationConfigEmbeddedWebApplicationContext(); + this.context.register(ClientConfiguration.class, + MinimalSecureWebApplication.class); + EnvironmentTestUtils.addEnvironment(this.context, + "security.oauth2.client.clientId=client", + "security.oauth2.client.grantType=client_credentials"); + this.context.refresh(); + // Thr primary context is fine (not session scoped): + assertThat(context.getBean(OAuth2ClientContext.class).getAccessTokenRequest()) + .isNotNull(); + assertThat(countBeans(ClientCredentialsResourceDetails.class)).isEqualTo(1); + /* + * Kind of a bug (should ideally be 1), but the cause is in Spring OAuth2 (there + * is no need for the extra session-scoped bean). What this test proves is that + * even if the user screws up and does @EnableOAuth2Client for client credentials, + * it will still just about work (because of the @Primary annotation on the + * Boot-created instance of OAuth2ClientContext). + */ + assertThat(countBeans(OAuth2ClientContext.class)).isEqualTo(2); + } + @Test public void testClientIsNotAuthCode() { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();