From 73a7b7727d7fd661670cbf5e789e4e2c6163f64c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Nicoll?= Date: Fri, 13 Mar 2026 17:20:11 +0000 Subject: [PATCH] Polish "Reject unknown JWS algorithms configured on NimbusJwtDecoder" See gh-50118 --- .../OAuth2ResourceServerJwtConfiguration.java | 21 ++++++++++++------- ...verterCustomizationsArgumentsProvider.java | 4 ++-- ...2ResourceServerAutoConfigurationTests.java | 4 +++- ...zationServerJwtAutoConfigurationTests.java | 3 ++- 4 files changed, 20 insertions(+), 12 deletions(-) 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 26dffa44fa0..e8ee88a469b 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 @@ -54,7 +54,9 @@ import org.springframework.security.oauth2.jwt.SupplierJwtDecoder; import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter; import org.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter; import org.springframework.security.web.SecurityFilterChain; +import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; +import org.springframework.util.StringUtils; import static org.springframework.security.config.Customizer.withDefaults; @@ -140,7 +142,7 @@ class OAuth2ResourceServerJwtConfiguration { RSAPublicKey publicKey = (RSAPublicKey) KeyFactory.getInstance("RSA") .generatePublic(new X509EncodedKeySpec(getKeySpec(this.properties.readPublicKey()))); NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder.withPublicKey(publicKey) - .signatureAlgorithm(SignatureAlgorithm.from(exactlyOneAlgorithm())) + .signatureAlgorithm(exactlyOneAlgorithm()) .build(); jwtDecoder.setJwtValidator(getValidators(JwtValidators.createDefault())); return jwtDecoder; @@ -151,15 +153,18 @@ class OAuth2ResourceServerJwtConfiguration { return Base64.getMimeDecoder().decode(keyValue); } - private String exactlyOneAlgorithm() { + private SignatureAlgorithm exactlyOneAlgorithm() { List algorithms = this.properties.getJwsAlgorithms(); - int count = (algorithms != null) ? algorithms.size() : 0; - if (count != 1) { - throw new IllegalStateException( - "Creating a JWT decoder using a public key requires exactly one JWS algorithm but " + count - + " were configured"); + Assert.state(algorithms != null && algorithms.size() == 1, + () -> "Creating a JWT decoder using a public key requires exactly one JWS algorithm but " + + algorithms.size() + " were configured"); + SignatureAlgorithm algorithm = SignatureAlgorithm.from(algorithms.get(0)); + if (algorithm == null) { + throw new InvalidConfigurationPropertyValueException( + "spring.security.oauth2.resourceserver.jwt.jws-algorithms", + StringUtils.collectionToCommaDelimitedString(algorithms), "Unknown algorithm"); } - return algorithms.get(0); + return algorithm; } @Bean 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 6566a10242b..c1527dd7477 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 @@ -70,10 +70,10 @@ public final class JwtConverterCustomizationsArgumentsProvider implements Argume .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) + Jwt customAuthoritiesClaimJwt = jwtBuilder.claim("scp", "value") .claim(customAuthoritiesClaim, jwtScopes[0] + " " + jwtScopes[1]) .build(); - Jwt customAuthoritiesClaimAndDelimiterJwt = jwtBuilder.claim("scp", null) + Jwt customAuthoritiesClaimAndDelimiterJwt = jwtBuilder.claim("scp", "value") .claim(customAuthoritiesClaim, jwtScopes[0] + "~" + jwtScopes[1]) .build(); String[] customPrefixAuthorities = { customPrefix + jwtScopes[0], customPrefix + jwtScopes[1] }; 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 47eb3ba01e4..4396e29c1e2 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 @@ -49,6 +49,7 @@ import org.mockito.InOrder; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.autoconfigure.security.oauth2.resource.JwtConverterCustomizationsArgumentsProvider; import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration; +import org.springframework.boot.context.properties.source.InvalidConfigurationPropertyValueException; import org.springframework.boot.test.context.FilteredClassLoader; import org.springframework.boot.test.context.assertj.AssertableWebApplicationContext; import org.springframework.boot.test.context.runner.WebApplicationContextRunner; @@ -333,7 +334,8 @@ class OAuth2ResourceServerAutoConfigurationTests { "spring.security.oauth2.resourceserver.jwt.jws-algorithms=NOT_VALID") .run((context) -> assertThat(context).hasFailed() .getFailure() - .hasMessageContaining("signatureAlgorithm cannot be null")); + .hasMessageContaining("Unknown algorithm") + .hasRootCauseExactlyInstanceOf(InvalidConfigurationPropertyValueException.class)); } @Test diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/server/servlet/OAuth2AuthorizationServerJwtAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/server/servlet/OAuth2AuthorizationServerJwtAutoConfigurationTests.java index d1717e26516..77cac57d11a 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/server/servlet/OAuth2AuthorizationServerJwtAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/server/servlet/OAuth2AuthorizationServerJwtAutoConfigurationTests.java @@ -31,6 +31,7 @@ import org.springframework.security.oauth2.jwt.NimbusJwtDecoder; import org.springframework.security.oauth2.server.authorization.OAuth2Authorization; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; /** * Tests for {@link OAuth2AuthorizationServerJwtAutoConfiguration}. @@ -96,7 +97,7 @@ class OAuth2AuthorizationServerJwtAutoConfigurationTests { @Bean JwtDecoder jwtDecoder() { - return (token) -> null; + return mock(JwtDecoder.class); } }