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