Allow user to signal that OAuth2 client is using client_credentials

By configuring security.oauth2.client.grantType=client_credentials the
user signals that (even in a web application) he doesn't want to use
the auth code grant (and hence session and request scoped beans for
client context).
pull/7634/merge
Dave Syer 8 years ago
parent 851ce2286f
commit 77a1a3b3c0
Notes: Phillip Webb 8 years ago
Fixes gh-5011

@ -16,16 +16,24 @@
package org.springframework.boot.autoconfigure.security.oauth2.client; 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 javax.annotation.Resource;
import org.springframework.beans.factory.annotation.Qualifier; 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.ConditionMessage;
import org.springframework.boot.autoconfigure.condition.ConditionOutcome; import org.springframework.boot.autoconfigure.condition.ConditionOutcome;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnNotWebApplication; 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.condition.SpringBootCondition;
import org.springframework.boot.autoconfigure.security.SecurityProperties; import org.springframework.boot.autoconfigure.security.SecurityProperties;
import org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2RestOperationsConfiguration.OAuth2ClientIdCondition; import org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2RestOperationsConfiguration.OAuth2ClientIdCondition;
@ -68,7 +76,7 @@ import org.springframework.util.StringUtils;
public class OAuth2RestOperationsConfiguration { public class OAuth2RestOperationsConfiguration {
@Configuration @Configuration
@ConditionalOnNotWebApplication @ConditionalOnClientCredentials
protected static class SingletonScopedConfiguration { protected static class SingletonScopedConfiguration {
@Bean @Bean
@ -88,7 +96,7 @@ public class OAuth2RestOperationsConfiguration {
@Configuration @Configuration
@ConditionalOnBean(OAuth2ClientConfiguration.class) @ConditionalOnBean(OAuth2ClientConfiguration.class)
@ConditionalOnWebApplication @ConditionalOnNotClientCredentials
@Import(OAuth2ProtectedResourceDetailsConfiguration.class) @Import(OAuth2ProtectedResourceDetailsConfiguration.class)
protected static class SessionScopedConfiguration { protected static class SessionScopedConfiguration {
@ -126,7 +134,7 @@ public class OAuth2RestOperationsConfiguration {
*/ */
@Configuration @Configuration
@ConditionalOnMissingBean(OAuth2ClientConfiguration.class) @ConditionalOnMissingBean(OAuth2ClientConfiguration.class)
@ConditionalOnWebApplication @ConditionalOnNotClientCredentials
@Import(OAuth2ProtectedResourceDetailsConfiguration.class) @Import(OAuth2ProtectedResourceDetailsConfiguration.class)
protected static class RequestScopedConfiguration { 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 {
}
}
} }

@ -21,6 +21,7 @@ import java.util.Arrays;
import java.util.List; import java.util.List;
import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.JsonNode;
import org.junit.Test; import org.junit.Test;
import org.springframework.aop.support.AopUtils; import org.springframework.aop.support.AopUtils;
@ -186,6 +187,44 @@ public class OAuth2AutoConfigurationTests {
assertThat(countBeans(OAuth2ClientContext.class)).isEqualTo(2); 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 @Test
public void testClientIsNotAuthCode() { public void testClientIsNotAuthCode() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();

Loading…
Cancel
Save