From baf52214a2fcad69644d566650cc0c66e24ea10f Mon Sep 17 00:00:00 2001 From: Moritz Halbritter Date: Wed, 10 Jan 2024 14:45:40 +0100 Subject: [PATCH] Polish "Auto-configure a JwtAuthenticationConverter" The JwtConverter bean is only supplied, if one of the following properties is there: * spring.security.oauth2.resourceserver.jwt.authority-prefix * spring.security.oauth2.resourceserver.jwt.principal-claim-name * spring.security.oauth2.resourceserver.jwt.authorities-claim-name See gh-38105 --- ...eOAuth2ResourceServerJwkConfiguration.java | 25 +++++++++++++++++++ .../OAuth2ResourceServerJwtConfiguration.java | 25 +++++++++++++++++++ ...verterCustomizationsArgumentsProvider.java | 14 ++--------- ...2ResourceServerAutoConfigurationTests.java | 24 ++++++++++++++++++ ...2ResourceServerAutoConfigurationTests.java | 23 +++++++++++++++++ 5 files changed, 99 insertions(+), 12 deletions(-) diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/resource/reactive/ReactiveOAuth2ResourceServerJwkConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/resource/reactive/ReactiveOAuth2ResourceServerJwkConfiguration.java index b00fce2b73..ffeb7bf977 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/resource/reactive/ReactiveOAuth2ResourceServerJwkConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/resource/reactive/ReactiveOAuth2ResourceServerJwkConfiguration.java @@ -26,6 +26,7 @@ import java.util.List; import java.util.Set; import org.springframework.beans.factory.ObjectProvider; +import org.springframework.boot.autoconfigure.condition.AnyNestedCondition; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; @@ -168,6 +169,7 @@ class ReactiveOAuth2ResourceServerJwkConfiguration { @Configuration(proxyBeanMethods = false) @ConditionalOnMissingBean(ReactiveJwtAuthenticationConverter.class) + @Conditional(JwtConverterPropertiesCondition.class) static class JwtConverterConfiguration { private final OAuth2ResourceServerProperties.Jwt properties; @@ -212,4 +214,27 @@ class ReactiveOAuth2ResourceServerJwkConfiguration { } + private static class JwtConverterPropertiesCondition extends AnyNestedCondition { + + JwtConverterPropertiesCondition() { + super(ConfigurationPhase.REGISTER_BEAN); + } + + @ConditionalOnProperty(prefix = "spring.security.oauth2.resourceserver.jwt", name = "authority-prefix") + static class OnAuthorityPrefix { + + } + + @ConditionalOnProperty(prefix = "spring.security.oauth2.resourceserver.jwt", name = "principal-claim-name") + static class OnPrincipalClaimName { + + } + + @ConditionalOnProperty(prefix = "spring.security.oauth2.resourceserver.jwt", name = "authorities-claim-name") + static class OnAuthoritiesClaimName { + + } + + } + } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/resource/servlet/OAuth2ResourceServerJwtConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/resource/servlet/OAuth2ResourceServerJwtConfiguration.java index b55e238a2f..7ba584815d 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/resource/servlet/OAuth2ResourceServerJwtConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/resource/servlet/OAuth2ResourceServerJwtConfiguration.java @@ -26,6 +26,7 @@ import java.util.List; import java.util.Set; import org.springframework.beans.factory.ObjectProvider; +import org.springframework.boot.autoconfigure.condition.AnyNestedCondition; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; @@ -179,6 +180,7 @@ class OAuth2ResourceServerJwtConfiguration { @Configuration(proxyBeanMethods = false) @ConditionalOnMissingBean(JwtAuthenticationConverter.class) + @Conditional(JwtConverterPropertiesCondition.class) static class JwtConverterConfiguration { private final OAuth2ResourceServerProperties.Jwt properties; @@ -204,4 +206,27 @@ class OAuth2ResourceServerJwtConfiguration { } + private static class JwtConverterPropertiesCondition extends AnyNestedCondition { + + JwtConverterPropertiesCondition() { + super(ConfigurationPhase.REGISTER_BEAN); + } + + @ConditionalOnProperty(prefix = "spring.security.oauth2.resourceserver.jwt", name = "authority-prefix") + static class OnAuthorityPrefix { + + } + + @ConditionalOnProperty(prefix = "spring.security.oauth2.resourceserver.jwt", name = "principal-claim-name") + static class OnPrincipalClaimName { + + } + + @ConditionalOnProperty(prefix = "spring.security.oauth2.resourceserver.jwt", name = "authorities-claim-name") + static class OnAuthoritiesClaimName { + + } + + } + } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/resource/JwtConverterCustomizationsArgumentsProvider.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/resource/JwtConverterCustomizationsArgumentsProvider.java index 45f4451735..cf40b50c44 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/resource/JwtConverterCustomizationsArgumentsProvider.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/resource/JwtConverterCustomizationsArgumentsProvider.java @@ -42,7 +42,6 @@ public final class JwtConverterCustomizationsArgumentsProvider implements Argume String customDelimiter = "[~,#:]"; String customAuthoritiesClaim = "custom_authorities"; String customPrincipalClaim = "custom_principal"; - String jwkSetUriProperty = "spring.security.oauth2.resourceserver.jwt.jwk-set-uri=https://jwk-set-uri.com"; String authorityPrefixProperty = "spring.security.oauth2.resourceserver.jwt.authority-prefix=" + customPrefix; String authoritiesDelimiterProperty = "spring.security.oauth2.resourceserver.jwt.authorities-claim-delimiter=" @@ -51,19 +50,15 @@ public final class JwtConverterCustomizationsArgumentsProvider implements Argume + customAuthoritiesClaim; String principalClaimProperty = "spring.security.oauth2.resourceserver.jwt.principal-claim-name=" + customPrincipalClaim; - - String[] noJwtConverterProps = { jwkSetUriProperty }; String[] customPrefixProps = { jwkSetUriProperty, authorityPrefixProperty }; - String[] customDelimiterProps = { jwkSetUriProperty, authoritiesDelimiterProperty }; + String[] customDelimiterProps = { jwkSetUriProperty, authorityPrefixProperty, authoritiesDelimiterProperty }; String[] customAuthoritiesClaimProps = { jwkSetUriProperty, authoritiesClaimProperty }; String[] customPrincipalClaimProps = { jwkSetUriProperty, principalClaimProperty }; String[] allJwtConverterProps = { jwkSetUriProperty, authorityPrefixProperty, authoritiesDelimiterProperty, authoritiesClaimProperty, principalClaimProperty }; - String[] jwtScopes = { "custom_scope0", "custom_scope1" }; String subjectValue = UUID.randomUUID().toString(); String customPrincipalValue = UUID.randomUUID().toString(); - Jwt.Builder jwtBuilder = Jwt.withTokenValue("token") .header("alg", "none") .expiresAt(Instant.MAX) @@ -73,7 +68,6 @@ public final class JwtConverterCustomizationsArgumentsProvider implements Argume .notBefore(Instant.MIN) .subject(subjectValue) .claim(customPrincipalClaim, customPrincipalValue); - Jwt noAuthoritiesCustomizationsJwt = jwtBuilder.claim("scp", jwtScopes[0] + " " + jwtScopes[1]).build(); Jwt customAuthoritiesDelimiterJwt = jwtBuilder.claim("scp", jwtScopes[0] + "~" + jwtScopes[1]).build(); Jwt customAuthoritiesClaimJwt = jwtBuilder.claim("scp", null) @@ -82,17 +76,13 @@ public final class JwtConverterCustomizationsArgumentsProvider implements Argume Jwt customAuthoritiesClaimAndDelimiterJwt = jwtBuilder.claim("scp", null) .claim(customAuthoritiesClaim, jwtScopes[0] + "~" + jwtScopes[1]) .build(); - String[] customPrefixAuthorities = { customPrefix + jwtScopes[0], customPrefix + jwtScopes[1] }; String[] defaultPrefixAuthorities = { "SCOPE_" + jwtScopes[0], "SCOPE_" + jwtScopes[1] }; - return Stream.of( - Arguments.of(Named.named("No JWT converter customizations", noJwtConverterProps), - noAuthoritiesCustomizationsJwt, subjectValue, defaultPrefixAuthorities), Arguments.of(Named.named("Custom prefix for GrantedAuthority", customPrefixProps), noAuthoritiesCustomizationsJwt, subjectValue, customPrefixAuthorities), Arguments.of(Named.named("Custom delimiter for JWT scopes", customDelimiterProps), - customAuthoritiesDelimiterJwt, subjectValue, defaultPrefixAuthorities), + customAuthoritiesDelimiterJwt, subjectValue, customPrefixAuthorities), Arguments.of(Named.named("Custom JWT authority claim name", customAuthoritiesClaimProps), customAuthoritiesClaimJwt, subjectValue, defaultPrefixAuthorities), Arguments.of(Named.named("Custom JWT principal claim name", customPrincipalClaimProps), diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/resource/reactive/ReactiveOAuth2ResourceServerAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/resource/reactive/ReactiveOAuth2ResourceServerAutoConfigurationTests.java index 5c4e359ce4..5f67fe9f17 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/resource/reactive/ReactiveOAuth2ResourceServerAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/resource/reactive/ReactiveOAuth2ResourceServerAutoConfigurationTests.java @@ -633,6 +633,30 @@ class ReactiveOAuth2ResourceServerAutoConfigurationTests { }); } + @Test + void shouldNotConfigureJwtConverterIfNoPropertiesAreSet() { + this.contextRunner + .run((context) -> assertThat(context).doesNotHaveBean(ReactiveJwtAuthenticationConverter.class)); + } + + @Test + void shouldConfigureJwtConverterIfPrincipalClaimNameIsSet() { + this.contextRunner.withPropertyValues("spring.security.oauth2.resourceserver.jwt.principal-claim-name=dummy") + .run((context) -> assertThat(context).hasSingleBean(ReactiveJwtAuthenticationConverter.class)); + } + + @Test + void shouldConfigureJwtConverterIfAuthorityPrefixIsSet() { + this.contextRunner.withPropertyValues("spring.security.oauth2.resourceserver.jwt.authority-prefix=dummy") + .run((context) -> assertThat(context).hasSingleBean(ReactiveJwtAuthenticationConverter.class)); + } + + @Test + void shouldConfigureJwtConverterIfAuthorityClaimsNameIsSet() { + this.contextRunner.withPropertyValues("spring.security.oauth2.resourceserver.jwt.authorities-claim-name=dummy") + .run((context) -> assertThat(context).hasSingleBean(ReactiveJwtAuthenticationConverter.class)); + } + @ParameterizedTest(name = "{0}") @ArgumentsSource(JwtConverterCustomizationsArgumentsProvider.class) void autoConfigurationShouldConfigureResourceServerWithJwtConverterCustomizations(String[] properties, Jwt jwt, diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/resource/servlet/OAuth2ResourceServerAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/resource/servlet/OAuth2ResourceServerAutoConfigurationTests.java index 758ac7b6aa..3c71539b81 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/resource/servlet/OAuth2ResourceServerAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/resource/servlet/OAuth2ResourceServerAutoConfigurationTests.java @@ -662,6 +662,29 @@ class OAuth2ResourceServerAutoConfigurationTests { }); } + @Test + void shouldNotConfigureJwtConverterIfNoPropertiesAreSet() { + this.contextRunner.run((context) -> assertThat(context).doesNotHaveBean(JwtAuthenticationConverter.class)); + } + + @Test + void shouldConfigureJwtConverterIfPrincipalClaimNameIsSet() { + this.contextRunner.withPropertyValues("spring.security.oauth2.resourceserver.jwt.principal-claim-name=dummy") + .run((context) -> assertThat(context).hasSingleBean(JwtAuthenticationConverter.class)); + } + + @Test + void shouldConfigureJwtConverterIfAuthorityPrefixIsSet() { + this.contextRunner.withPropertyValues("spring.security.oauth2.resourceserver.jwt.authority-prefix=dummy") + .run((context) -> assertThat(context).hasSingleBean(JwtAuthenticationConverter.class)); + } + + @Test + void shouldConfigureJwtConverterIfAuthorityClaimsNameIsSet() { + this.contextRunner.withPropertyValues("spring.security.oauth2.resourceserver.jwt.authorities-claim-name=dummy") + .run((context) -> assertThat(context).hasSingleBean(JwtAuthenticationConverter.class)); + } + @Test void jwtAuthenticationConverterByJwtConfigIsConditionalOnMissingBean() { String propertiesPrincipalClaim = "principal_from_properties";