From 7054a33e70826b8d88e46494b332171df761f5fd Mon Sep 17 00:00:00 2001 From: Madhura Bhave Date: Mon, 8 Apr 2019 16:14:18 -0700 Subject: [PATCH] Add support for public key file for OAuth2 resource server Closes gh-15814 --- .../oauth2/resource/KeyValueCondition.java | 63 ++++++++++++++++++ .../OAuth2ResourceServerProperties.java | 13 ++++ ...eOAuth2ResourceServerJwkConfiguration.java | 38 +++++++++-- .../OAuth2ResourceServerJwtConfiguration.java | 36 +++++++++-- ...2ResourceServerAutoConfigurationTests.java | 41 ++++++++++-- ...2ResourceServerAutoConfigurationTests.java | 64 +++++++++++++++---- .../src/test/resources/public-key-location | 6 ++ .../spring-boot-dependencies/pom.xml | 2 +- .../main/asciidoc/spring-boot-features.adoc | 6 ++ 9 files changed, 238 insertions(+), 31 deletions(-) create mode 100644 spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/resource/KeyValueCondition.java create mode 100644 spring-boot-project/spring-boot-autoconfigure/src/test/resources/public-key-location diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/resource/KeyValueCondition.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/resource/KeyValueCondition.java new file mode 100644 index 0000000000..caacae0fa0 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/resource/KeyValueCondition.java @@ -0,0 +1,63 @@ +/* + * Copyright 2012-2019 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.autoconfigure.security.oauth2.resource; + +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 public key value. + * + * @author Madhura Bhave + * @since 2.2.0 + */ +public class KeyValueCondition extends SpringBootCondition { + + @Override + public ConditionOutcome getMatchOutcome(ConditionContext context, + AnnotatedTypeMetadata metadata) { + ConditionMessage.Builder message = ConditionMessage + .forCondition("Public Key Value Condition"); + Environment environment = context.getEnvironment(); + String publicKeyLocation = environment.getProperty( + "spring.security.oauth2.resourceserver.jwt.public-key-location"); + if (!StringUtils.hasText(publicKeyLocation)) { + return ConditionOutcome + .noMatch(message.didNotFind("issuer-uri property").atAll()); + } + String issuerUri = environment + .getProperty("spring.security.oauth2.resourceserver.jwt.issuer-uri"); + String jwkSetUri = environment + .getProperty("spring.security.oauth2.resourceserver.jwt.jwk-set-uri"); + if (StringUtils.hasText(jwkSetUri)) { + return ConditionOutcome + .noMatch(message.found("jwk-set-uri property").items(jwkSetUri)); + } + if (StringUtils.hasText(issuerUri)) { + return ConditionOutcome + .noMatch(message.found("issuer-uri property").items(issuerUri)); + } + return ConditionOutcome + .match(message.foundExactly("public key location property")); + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/resource/OAuth2ResourceServerProperties.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/resource/OAuth2ResourceServerProperties.java index 3989db2760..4dcaa7aed3 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/resource/OAuth2ResourceServerProperties.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/resource/OAuth2ResourceServerProperties.java @@ -50,6 +50,11 @@ public class OAuth2ResourceServerProperties { */ private String issuerUri; + /** + * Location of the file containing the public key used to verify a JWT. + */ + private String publicKeyLocation; + public String getJwkSetUri() { return this.jwkSetUri; } @@ -74,6 +79,14 @@ public class OAuth2ResourceServerProperties { this.issuerUri = issuerUri; } + public String getPublicKeyLocation() { + return this.publicKeyLocation; + } + + public void setPublicKeyLocation(String publicKeyLocation) { + this.publicKeyLocation = publicKeyLocation; + } + } } 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 5bf81b6717..93efdf47ed 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 @@ -15,9 +15,16 @@ */ package org.springframework.boot.autoconfigure.security.oauth2.resource.reactive; +import java.io.InputStreamReader; +import java.security.KeyFactory; +import java.security.interfaces.RSAPublicKey; +import java.security.spec.X509EncodedKeySpec; +import java.util.Base64; + import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.security.oauth2.resource.IssuerUriCondition; +import org.springframework.boot.autoconfigure.security.oauth2.resource.KeyValueCondition; import org.springframework.boot.autoconfigure.security.oauth2.resource.OAuth2ResourceServerProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Conditional; @@ -25,9 +32,12 @@ import org.springframework.context.annotation.Configuration; import org.springframework.security.oauth2.jwt.NimbusReactiveJwtDecoder; import org.springframework.security.oauth2.jwt.ReactiveJwtDecoder; import org.springframework.security.oauth2.jwt.ReactiveJwtDecoders; +import org.springframework.util.FileCopyUtils; +import org.springframework.util.ResourceUtils; /** - * Configures a {@link ReactiveJwtDecoder} when a JWK Set URI is available. + * Configures a {@link ReactiveJwtDecoder} when a JWK Set URI, OpenID Connect Issuer URI + * or Public Key configuration is available. * * @author Madhura Bhave * @author Artsiom Yudovin @@ -35,26 +45,42 @@ import org.springframework.security.oauth2.jwt.ReactiveJwtDecoders; @Configuration(proxyBeanMethods = false) class ReactiveOAuth2ResourceServerJwkConfiguration { - private final OAuth2ResourceServerProperties properties; + private final OAuth2ResourceServerProperties.Jwt properties; ReactiveOAuth2ResourceServerJwkConfiguration( OAuth2ResourceServerProperties properties) { - this.properties = properties; + this.properties = properties.getJwt(); } @Bean @ConditionalOnProperty(name = "spring.security.oauth2.resourceserver.jwt.jwk-set-uri") @ConditionalOnMissingBean public ReactiveJwtDecoder jwtDecoder() { - return new NimbusReactiveJwtDecoder(this.properties.getJwt().getJwkSetUri()); + return new NimbusReactiveJwtDecoder(this.properties.getJwkSetUri()); + } + + @Bean + @Conditional(KeyValueCondition.class) + @ConditionalOnMissingBean + public NimbusReactiveJwtDecoder jwtDecoderByPublicKeyValue() throws Exception { + String keyValue = FileCopyUtils.copyToString(new InputStreamReader(ResourceUtils + .getURL(this.properties.getPublicKeyLocation()).openStream())); + RSAPublicKey publicKey = (RSAPublicKey) KeyFactory.getInstance("RSA") + .generatePublic(new X509EncodedKeySpec(getKeySpec(keyValue))); + return NimbusReactiveJwtDecoder.withPublicKey(publicKey).build(); + } + + private byte[] getKeySpec(String keyValue) { + keyValue = keyValue.replace("-----BEGIN PUBLIC KEY-----", "") + .replace("-----END PUBLIC KEY-----", "").replace("\n", ""); + return Base64.getDecoder().decode(keyValue); } @Bean @Conditional(IssuerUriCondition.class) @ConditionalOnMissingBean public ReactiveJwtDecoder jwtDecoderByIssuerUri() { - return ReactiveJwtDecoders - .fromOidcIssuerLocation(this.properties.getJwt().getIssuerUri()); + return ReactiveJwtDecoders.fromOidcIssuerLocation(this.properties.getIssuerUri()); } } 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 449e273f2d..16d106ceca 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 @@ -15,20 +15,29 @@ */ package org.springframework.boot.autoconfigure.security.oauth2.resource.servlet; +import java.io.InputStreamReader; +import java.security.KeyFactory; +import java.security.interfaces.RSAPublicKey; +import java.security.spec.X509EncodedKeySpec; +import java.util.Base64; + import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.security.oauth2.resource.IssuerUriCondition; +import org.springframework.boot.autoconfigure.security.oauth2.resource.KeyValueCondition; import org.springframework.boot.autoconfigure.security.oauth2.resource.OAuth2ResourceServerProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Configuration; import org.springframework.security.oauth2.jwt.JwtDecoder; import org.springframework.security.oauth2.jwt.JwtDecoders; -import org.springframework.security.oauth2.jwt.NimbusJwtDecoderJwkSupport; +import org.springframework.security.oauth2.jwt.NimbusJwtDecoder; +import org.springframework.util.FileCopyUtils; +import org.springframework.util.ResourceUtils; /** - * Configures a {@link JwtDecoder} when a JWK Set URI or OpenID Connect Issuer URI is - * available. + * Configures a {@link JwtDecoder} when a JWK Set URI, OpenID Connect Issuer URI or Public + * Key configuration is available. * * @author Madhura Bhave * @author Artsiom Yudovin @@ -46,8 +55,25 @@ class OAuth2ResourceServerJwtConfiguration { @ConditionalOnProperty(name = "spring.security.oauth2.resourceserver.jwt.jwk-set-uri") @ConditionalOnMissingBean public JwtDecoder jwtDecoderByJwkKeySetUri() { - return new NimbusJwtDecoderJwkSupport(this.properties.getJwkSetUri(), - this.properties.getJwsAlgorithm()); + return NimbusJwtDecoder.withJwkSetUri(this.properties.getJwkSetUri()) + .jwsAlgorithm(this.properties.getJwsAlgorithm()).build(); + } + + @Bean + @Conditional(KeyValueCondition.class) + @ConditionalOnMissingBean + public JwtDecoder jwtDecoderByPublicKeyValue() throws Exception { + String keyValue = FileCopyUtils.copyToString(new InputStreamReader(ResourceUtils + .getURL(this.properties.getPublicKeyLocation()).openStream())); + RSAPublicKey publicKey = (RSAPublicKey) KeyFactory.getInstance("RSA") + .generatePublic(new X509EncodedKeySpec(getKeySpec(keyValue))); + return NimbusJwtDecoder.withPublicKey(publicKey).build(); + } + + private byte[] getKeySpec(String keyValue) { + keyValue = keyValue.replace("-----BEGIN PUBLIC KEY-----", "") + .replace("-----END PUBLIC KEY-----", "").replace("\n", ""); + return Base64.getDecoder().decode(keyValue); } @Bean 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 4ceabb921b..5b81bedeff 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 @@ -82,8 +82,7 @@ public class ReactiveOAuth2ResourceServerAutoConfigurationTests { this.contextRunner.withPropertyValues( "spring.security.oauth2.resourceserver.jwt.jwk-set-uri=https://jwk-set-uri.com") .run((context) -> { - assertThat(context.getBean(ReactiveJwtDecoder.class)) - .isInstanceOf(NimbusReactiveJwtDecoder.class); + assertThat(context).hasSingleBean(NimbusReactiveJwtDecoder.class); assertFilterConfiguredWithJwtAuthenticationManager(context); }); } @@ -101,26 +100,54 @@ public class ReactiveOAuth2ResourceServerAutoConfigurationTests { "spring.security.oauth2.resourceserver.jwt.issuer-uri=http://" + this.server.getHostName() + ":" + this.server.getPort()) .run((context) -> { - assertThat(context.getBean(ReactiveJwtDecoder.class)) - .isInstanceOf(NimbusReactiveJwtDecoder.class); + assertThat(context).hasSingleBean(NimbusReactiveJwtDecoder.class); assertFilterConfiguredWithJwtAuthenticationManager(context); }); } @Test - public void autoConfigurationWhenBothSetUriAndIssuerUriPresentShouldUseSetUri() { + public void autoConfigurationShouldConfigureResourceServerUsingPublicKeyValue() { + this.contextRunner.withPropertyValues( + "spring.security.oauth2.resourceserver.jwt.public-key-location=classpath:public-key-location") + .run((context) -> { + assertThat(context).hasSingleBean(NimbusReactiveJwtDecoder.class); + assertFilterConfiguredWithJwtAuthenticationManager(context); + }); + } + + @Test + public void autoConfigurationWhenSetUriKeyLocationIssuerUriPresentShouldUseSetUri() { this.contextRunner.withPropertyValues( "spring.security.oauth2.resourceserver.jwt.jwk-set-uri=https://jwk-set-uri.com", + "spring.security.oauth2.resourceserver.jwt.public-key-location=classpath:public-key-location", "spring.security.oauth2.resourceserver.jwt.issuer-uri=https://jwk-oidc-issuer-location.com") .run((context) -> { - assertThat(context.getBean(ReactiveJwtDecoder.class)) - .isInstanceOf(NimbusReactiveJwtDecoder.class); + assertThat(context).hasSingleBean(NimbusReactiveJwtDecoder.class); assertFilterConfiguredWithJwtAuthenticationManager(context); assertThat(context.containsBean("jwtDecoder")).isTrue(); assertThat(context.containsBean("jwtDecoderByIssuerUri")).isFalse(); }); } + @Test + public void autoConfigurationWhenKeyLocationAndIssuerUriPresentShouldUseIssuerUri() + throws Exception { + this.server = new MockWebServer(); + this.server.start(); + String issuer = this.server.url("").toString(); + String cleanIssuerPath = cleanIssuerPath(issuer); + setupMockResponse(cleanIssuerPath); + this.contextRunner.withPropertyValues( + "spring.security.oauth2.resourceserver.jwt.issuer-uri=http://" + + this.server.getHostName() + ":" + this.server.getPort(), + "spring.security.oauth2.resourceserver.jwt.public-key-location=classpath:public-key-location") + .run((context) -> { + assertThat(context).hasSingleBean(NimbusReactiveJwtDecoder.class); + assertFilterConfiguredWithJwtAuthenticationManager(context); + assertThat(context.containsBean("jwtDecoderByIssuerUri")).isTrue(); + }); + } + @Test public void autoConfigurationWhenJwkSetUriNullShouldNotFail() { this.contextRunner.run((context) -> assertThat(context) 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 132bd97c40..6ec6fc8059 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 @@ -42,11 +42,11 @@ import org.springframework.http.MediaType; import org.springframework.security.config.BeanIds; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.oauth2.jwt.JwtDecoder; -import org.springframework.security.oauth2.jwt.NimbusJwtDecoderJwkSupport; import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken; import org.springframework.security.oauth2.server.resource.web.BearerTokenAuthenticationFilter; import org.springframework.security.web.FilterChainProxy; import org.springframework.security.web.SecurityFilterChain; +import org.springframework.test.util.ReflectionTestUtils; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; @@ -78,8 +78,7 @@ public class OAuth2ResourceServerAutoConfigurationTests { this.contextRunner.withPropertyValues( "spring.security.oauth2.resourceserver.jwt.jwk-set-uri=https://jwk-set-uri.com") .run((context) -> { - assertThat(context.getBean(JwtDecoder.class)) - .isInstanceOf(NimbusJwtDecoderJwkSupport.class); + assertThat(context).hasSingleBean(JwtDecoder.class); assertThat(getBearerTokenFilter(context)).isNotNull(); }); } @@ -90,7 +89,11 @@ public class OAuth2ResourceServerAutoConfigurationTests { "spring.security.oauth2.resourceserver.jwt.jwk-set-uri=https://jwk-set-uri.com") .run((context) -> { JwtDecoder jwtDecoder = context.getBean(JwtDecoder.class); - assertThat(jwtDecoder).hasFieldOrPropertyWithValue("jwsAlgorithm", + Object processor = ReflectionTestUtils.getField(jwtDecoder, + "jwtProcessor"); + Object keySelector = ReflectionTestUtils.getField(processor, + "jwsKeySelector"); + assertThat(keySelector).hasFieldOrPropertyWithValue("jwsAlg", JWSAlgorithm.RS256); }); } @@ -102,7 +105,11 @@ public class OAuth2ResourceServerAutoConfigurationTests { "spring.security.oauth2.resourceserver.jwt.jws-algorithm=HS512") .run((context) -> { JwtDecoder jwtDecoder = context.getBean(JwtDecoder.class); - assertThat(jwtDecoder).hasFieldOrPropertyWithValue("jwsAlgorithm", + Object processor = ReflectionTestUtils.getField(jwtDecoder, + "jwtProcessor"); + Object keySelector = ReflectionTestUtils.getField(processor, + "jwsKeySelector"); + assertThat(keySelector).hasFieldOrPropertyWithValue("jwsAlg", JWSAlgorithm.HS512); assertThat(getBearerTokenFilter(context)).isNotNull(); }); @@ -121,24 +128,57 @@ public class OAuth2ResourceServerAutoConfigurationTests { "spring.security.oauth2.resourceserver.jwt.issuer-uri=http://" + this.server.getHostName() + ":" + this.server.getPort()) .run((context) -> { - assertThat(context.getBean(JwtDecoder.class)) - .isInstanceOf(NimbusJwtDecoderJwkSupport.class); + assertThat(context).hasSingleBean(JwtDecoder.class); assertThat(getBearerTokenFilter(context)).isNotNull(); }); } @Test - public void autoConfigurationWhenBothSetUriAndIssuerUriPresentShouldUseSetUri() { + public void autoConfigurationShouldConfigureResourceServerUsingPublicKeyValue() + throws Exception { + this.server = new MockWebServer(); + this.server.start(); + String issuer = this.server.url("").toString(); + String cleanIssuerPath = cleanIssuerPath(issuer); + setupMockResponse(cleanIssuerPath); + this.contextRunner.withPropertyValues( + "spring.security.oauth2.resourceserver.jwt.public-key-location=classpath:public-key-location") + .run((context) -> { + assertThat(context).hasSingleBean(JwtDecoder.class); + assertThat(getBearerTokenFilter(context)).isNotNull(); + }); + } + + @Test + public void autoConfigurationWhenSetUriKeyLocationAndIssuerUriPresentShouldUseSetUri() { this.contextRunner.withPropertyValues( "spring.security.oauth2.resourceserver.jwt.issuer-uri=https://issuer-uri.com", + "spring.security.oauth2.resourceserver.jwt.public-key-location=classpath:public-key-location", "spring.security.oauth2.resourceserver.jwt.jwk-set-uri=https://jwk-set-uri.com") .run((context) -> { - assertThat(context.getBean(JwtDecoder.class)) - .isInstanceOf(NimbusJwtDecoderJwkSupport.class); + assertThat(context).hasSingleBean(JwtDecoder.class); assertThat(getBearerTokenFilter(context)).isNotNull(); assertThat(context.containsBean("jwtDecoderByJwkKeySetUri")).isTrue(); - assertThat(context.containsBean("jwtDecoderByOidcIssuerUri")) - .isFalse(); + assertThat(context.containsBean("jwtDecoderByIssuerUri")).isFalse(); + }); + } + + @Test + public void autoConfigurationWhenKeyLocationAndIssuerUriPresentShouldUseIssuerUri() + throws Exception { + this.server = new MockWebServer(); + this.server.start(); + String issuer = this.server.url("").toString(); + String cleanIssuerPath = cleanIssuerPath(issuer); + setupMockResponse(cleanIssuerPath); + this.contextRunner.withPropertyValues( + "spring.security.oauth2.resourceserver.jwt.issuer-uri=http://" + + this.server.getHostName() + ":" + this.server.getPort(), + "spring.security.oauth2.resourceserver.jwt.public-key-location=classpath:public-key-location") + .run((context) -> { + assertThat(context).hasSingleBean(JwtDecoder.class); + assertThat(getBearerTokenFilter(context)).isNotNull(); + assertThat(context.containsBean("jwtDecoderByIssuerUri")).isTrue(); }); } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/resources/public-key-location b/spring-boot-project/spring-boot-autoconfigure/src/test/resources/public-key-location new file mode 100644 index 0000000000..d27e5587ac --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/resources/public-key-location @@ -0,0 +1,6 @@ +-----BEGIN PUBLIC KEY----- +MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDdlatRjRjogo3WojgGHFHYLugd +UWAY9iR3fy4arWNA1KoS8kVw33cJibXr8bvwUAUparCwlvdbH6dvEOfou0/gCFQs +HUfQrSDv+MuSUMAe8jzKE4qW+jK+xQU9a03GUnKHkkle+Q0pX/g6jXZ7r1/xAK5D +o2kQ+X5xK9cipRgEKwIDAQAB +-----END PUBLIC KEY----- \ No newline at end of file diff --git a/spring-boot-project/spring-boot-dependencies/pom.xml b/spring-boot-project/spring-boot-dependencies/pom.xml index 6e6cd5d8fa..12406de54b 100644 --- a/spring-boot-project/spring-boot-dependencies/pom.xml +++ b/spring-boot-project/spring-boot-dependencies/pom.xml @@ -190,7 +190,7 @@ 2.0.0.M1 2.0.3.RELEASE 1.2.4.RELEASE - 5.1.5.RELEASE + 5.2.0.BUILD-SNAPSHOT Bean-SR4 3.0.7.RELEASE 3.27.2 diff --git a/spring-boot-project/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc b/spring-boot-project/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc index df50149a95..1549fc802a 100644 --- a/spring-boot-project/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc +++ b/spring-boot-project/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc @@ -3657,6 +3657,12 @@ as shown in the following examples: spring.security.oauth2.resourceserver.jwt.issuer-uri=https://dev-123456.oktapreview.com/oauth2/default/ ---- +NOTE: If the authorization server does not support a JWK Set URI, you can configure the +resource server with the Public Key used for verifying the signature of the JWT. This can +be done using the `spring.security.oauth2.resourceserver.jwt.public-key-location` property, +where the value needs to point to a file containing the public key in the PEM-encoded x509 +format. + The same properties are applicable for both servlet and reactive applications. Alternatively, you can define your own `JwtDecoder` bean for servlet applications