diff --git a/module/spring-boot-security-oauth2-resource-server/src/main/java/org/springframework/boot/security/oauth2/server/resource/autoconfigure/ConditionalOnJwkSetUriJwtDecoder.java b/module/spring-boot-security-oauth2-resource-server/src/main/java/org/springframework/boot/security/oauth2/server/resource/autoconfigure/ConditionalOnJwkSetUriJwtDecoder.java new file mode 100644 index 00000000000..c6793b353de --- /dev/null +++ b/module/spring-boot-security-oauth2-resource-server/src/main/java/org/springframework/boot/security/oauth2/server/resource/autoconfigure/ConditionalOnJwkSetUriJwtDecoder.java @@ -0,0 +1,42 @@ +/* + * Copyright 2012-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.security.oauth2.server.resource.autoconfigure; + +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 org.springframework.context.annotation.Conditional; +import org.springframework.security.oauth2.jwt.NimbusJwtDecoder; +import org.springframework.security.oauth2.jwt.NimbusReactiveJwtDecoder; + +/** + * Condition that matches when a JWK Set URI based {@link NimbusJwtDecoder} or + * {@link NimbusReactiveJwtDecoder} should be used. + * + * @author Andy Wilkinson + * @since 4.0.8 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.TYPE, ElementType.METHOD }) +@Documented +@Conditional(JwkSetUriCondition.class) +public @interface ConditionalOnJwkSetUriJwtDecoder { + +} diff --git a/module/spring-boot-security-oauth2-resource-server/src/main/java/org/springframework/boot/security/oauth2/server/resource/autoconfigure/JwkSetUriCondition.java b/module/spring-boot-security-oauth2-resource-server/src/main/java/org/springframework/boot/security/oauth2/server/resource/autoconfigure/JwkSetUriCondition.java new file mode 100644 index 00000000000..589d0b74b89 --- /dev/null +++ b/module/spring-boot-security-oauth2-resource-server/src/main/java/org/springframework/boot/security/oauth2/server/resource/autoconfigure/JwkSetUriCondition.java @@ -0,0 +1,45 @@ +/* + * Copyright 2012-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.security.oauth2.server.resource.autoconfigure; + +import org.springframework.boot.autoconfigure.condition.ConditionMessage; +import org.springframework.boot.autoconfigure.condition.ConditionOutcome; +import org.springframework.boot.autoconfigure.condition.SpringBootCondition; +import org.springframework.context.annotation.ConditionContext; +import org.springframework.core.env.Environment; +import org.springframework.core.type.AnnotatedTypeMetadata; +import org.springframework.util.StringUtils; + +/** + * Condition for creating a JWT decoder using a JWK Set URI. + * + * @author Vinod Kumar M + */ +class JwkSetUriCondition extends SpringBootCondition { + + @Override + public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) { + ConditionMessage.Builder message = ConditionMessage.forCondition("JWK Set URI Condition"); + Environment environment = context.getEnvironment(); + String jwkSetUri = environment.getProperty("spring.security.oauth2.resourceserver.jwt.jwk-set-uri"); + if (!StringUtils.hasText(jwkSetUri)) { + return ConditionOutcome.noMatch(message.didNotFind("jwk-set-uri property").atAll()); + } + return ConditionOutcome.match(message.foundExactly("jwk-set-uri property")); + } + +} diff --git a/module/spring-boot-security-oauth2-resource-server/src/main/java/org/springframework/boot/security/oauth2/server/resource/autoconfigure/JwtDecoderConfiguration.java b/module/spring-boot-security-oauth2-resource-server/src/main/java/org/springframework/boot/security/oauth2/server/resource/autoconfigure/JwtDecoderConfiguration.java index a1de6887c37..00e85190b26 100644 --- a/module/spring-boot-security-oauth2-resource-server/src/main/java/org/springframework/boot/security/oauth2/server/resource/autoconfigure/JwtDecoderConfiguration.java +++ b/module/spring-boot-security-oauth2-resource-server/src/main/java/org/springframework/boot/security/oauth2/server/resource/autoconfigure/JwtDecoderConfiguration.java @@ -30,7 +30,6 @@ import org.jspecify.annotations.Nullable; import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.source.InvalidConfigurationPropertyValueException; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -115,7 +114,7 @@ class JwtDecoderConfiguration { } @Bean - @ConditionalOnProperty(name = "spring.security.oauth2.resourceserver.jwt.jwk-set-uri") + @ConditionalOnJwkSetUriJwtDecoder JwtDecoder jwtDecoderByJwkKeySetUri() { String jwkSetUri = this.properties.getJwkSetUri(); Assert.state(jwkSetUri != null, "No JWK Set URI property specified"); diff --git a/module/spring-boot-security-oauth2-resource-server/src/main/java/org/springframework/boot/security/oauth2/server/resource/autoconfigure/reactive/ReactiveJwtDecoderConfiguration.java b/module/spring-boot-security-oauth2-resource-server/src/main/java/org/springframework/boot/security/oauth2/server/resource/autoconfigure/reactive/ReactiveJwtDecoderConfiguration.java index 51da5fbc957..fb68d63f7b2 100644 --- a/module/spring-boot-security-oauth2-resource-server/src/main/java/org/springframework/boot/security/oauth2/server/resource/autoconfigure/reactive/ReactiveJwtDecoderConfiguration.java +++ b/module/spring-boot-security-oauth2-resource-server/src/main/java/org/springframework/boot/security/oauth2/server/resource/autoconfigure/reactive/ReactiveJwtDecoderConfiguration.java @@ -33,9 +33,9 @@ import org.jspecify.annotations.Nullable; import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.source.InvalidConfigurationPropertyValueException; import org.springframework.boot.security.oauth2.server.resource.autoconfigure.ConditionalOnIssuerLocationJwtDecoder; +import org.springframework.boot.security.oauth2.server.resource.autoconfigure.ConditionalOnJwkSetUriJwtDecoder; import org.springframework.boot.security.oauth2.server.resource.autoconfigure.ConditionalOnPublicKeyJwtDecoder; import org.springframework.boot.security.oauth2.server.resource.autoconfigure.OAuth2ResourceServerProperties; import org.springframework.context.annotation.Bean; @@ -122,7 +122,7 @@ class ReactiveJwtDecoderConfiguration { } @Bean - @ConditionalOnProperty(name = "spring.security.oauth2.resourceserver.jwt.jwk-set-uri") + @ConditionalOnJwkSetUriJwtDecoder ReactiveJwtDecoder reactiveJwtDecoderByJwkKeySetUri() { String jwkSetUri = this.properties.getJwkSetUri(); Assert.notNull(jwkSetUri, "No JWK Set URI specified"); diff --git a/module/spring-boot-security-oauth2-resource-server/src/test/java/org/springframework/boot/security/oauth2/server/resource/autoconfigure/OAuth2ResourceServerAutoConfigurationTests.java b/module/spring-boot-security-oauth2-resource-server/src/test/java/org/springframework/boot/security/oauth2/server/resource/autoconfigure/OAuth2ResourceServerAutoConfigurationTests.java index b439800b9f6..a4900f10a34 100644 --- a/module/spring-boot-security-oauth2-resource-server/src/test/java/org/springframework/boot/security/oauth2/server/resource/autoconfigure/OAuth2ResourceServerAutoConfigurationTests.java +++ b/module/spring-boot-security-oauth2-resource-server/src/test/java/org/springframework/boot/security/oauth2/server/resource/autoconfigure/OAuth2ResourceServerAutoConfigurationTests.java @@ -347,6 +347,18 @@ class OAuth2ResourceServerAutoConfigurationTests { }); } + @Test + void autoConfigurationWhenIssuerUriPresentAndJwkSetUriEmptyShouldUseIssuerUri() { + this.contextRunner + .withPropertyValues("spring.security.oauth2.resourceserver.jwt.issuer-uri=https://issuer-uri.com", + "spring.security.oauth2.resourceserver.jwt.jwk-set-uri=") + .run((context) -> { + assertThat(context).hasSingleBean(JwtDecoder.class); + assertThat(context.containsBean("jwtDecoderByJwkKeySetUri")).isFalse(); + assertThat(context.containsBean("jwtDecoderByIssuerUri")).isTrue(); + }); + } + @Test void autoConfigurationWhenKeyLocationAndIssuerUriPresentShouldUseIssuerUri() throws Exception { this.server = new MockWebServer(); diff --git a/module/spring-boot-security-oauth2-resource-server/src/test/java/org/springframework/boot/security/oauth2/server/resource/autoconfigure/reactive/ReactiveOAuth2ResourceServerAutoConfigurationTests.java b/module/spring-boot-security-oauth2-resource-server/src/test/java/org/springframework/boot/security/oauth2/server/resource/autoconfigure/reactive/ReactiveOAuth2ResourceServerAutoConfigurationTests.java index a0d5afaaebd..ef1ab88f750 100644 --- a/module/spring-boot-security-oauth2-resource-server/src/test/java/org/springframework/boot/security/oauth2/server/resource/autoconfigure/reactive/ReactiveOAuth2ResourceServerAutoConfigurationTests.java +++ b/module/spring-boot-security-oauth2-resource-server/src/test/java/org/springframework/boot/security/oauth2/server/resource/autoconfigure/reactive/ReactiveOAuth2ResourceServerAutoConfigurationTests.java @@ -471,6 +471,18 @@ class ReactiveOAuth2ResourceServerAutoConfigurationTests { }); } + @Test + void autoConfigurationWhenIssuerUriPresentAndJwkSetUriEmptyShouldUseIssuerUri() { + this.contextRunner + .withPropertyValues("spring.security.oauth2.resourceserver.jwt.issuer-uri=https://issuer-uri.com", + "spring.security.oauth2.resourceserver.jwt.jwk-set-uri=") + .run((context) -> { + assertThat(context).hasSingleBean(ReactiveJwtDecoder.class); + assertThat(context.containsBean("reactiveJwtDecoderByJwkKeySetUri")).isFalse(); + assertThat(context.containsBean("reactiveJwtDecoderByIssuerUri")).isTrue(); + }); + } + @Test void opaqueTokenIntrospectorIsConditionalOnMissingBean() { this.contextRunner