diff --git a/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/authentication/JwtIssuerReactiveAuthenticationManagerResolver.java b/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/authentication/JwtIssuerReactiveAuthenticationManagerResolver.java index c2f415378b..f6ca3e5f66 100644 --- a/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/authentication/JwtIssuerReactiveAuthenticationManagerResolver.java +++ b/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/authentication/JwtIssuerReactiveAuthenticationManagerResolver.java @@ -16,6 +16,7 @@ package org.springframework.security.oauth2.server.resource.authentication; +import java.time.Duration; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -170,7 +171,7 @@ public final class JwtIssuerReactiveAuthenticationManagerResolver new JwtReactiveAuthenticationManager(ReactiveJwtDecoders.fromIssuerLocation(k)) ) .subscribeOn(Schedulers.boundedElastic()) - .cache()); + .cache((manager) -> Duration.ofMillis(Long.MAX_VALUE), (ex) -> Duration.ZERO, () -> Duration.ZERO)); } } } diff --git a/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/authentication/JwtIssuerAuthenticationManagerResolverTests.java b/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/authentication/JwtIssuerAuthenticationManagerResolverTests.java index 21df5189fe..e4d8e6d34d 100644 --- a/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/authentication/JwtIssuerAuthenticationManagerResolverTests.java +++ b/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/authentication/JwtIssuerAuthenticationManagerResolverTests.java @@ -38,9 +38,11 @@ import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.AuthenticationManagerResolver; import org.springframework.security.oauth2.core.OAuth2AuthenticationException; import org.springframework.security.oauth2.jose.TestKeys; +import org.springframework.security.oauth2.jwt.JwtClaimNames; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatCode; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.mockito.Mockito.mock; import static org.springframework.security.oauth2.jwt.JwtClaimNames.ISS; @@ -85,6 +87,35 @@ public class JwtIssuerAuthenticationManagerResolverTests { } } + @Test + public void resolveWhenIssuerFailsThenErrorNotCached() throws Exception { + try (MockWebServer server = new MockWebServer()) { + server.start(); + String issuer = server.url("").toString(); + // @formatter:off + server.enqueue(new MockResponse().setResponseCode(500) + .setHeader("Content-Type", "application/json") + .setBody(String.format(DEFAULT_RESPONSE_TEMPLATE, issuer, issuer)) + ); + server.enqueue(new MockResponse().setResponseCode(200) + .setHeader("Content-Type", "application/json") + .setBody(String.format(DEFAULT_RESPONSE_TEMPLATE, issuer, issuer)) + ); + // @formatter:on + JWSObject jws = new JWSObject(new JWSHeader(JWSAlgorithm.RS256), + new Payload(new JSONObject(Collections.singletonMap(JwtClaimNames.ISS, issuer)))); + jws.sign(new RSASSASigner(TestKeys.DEFAULT_PRIVATE_KEY)); + JwtIssuerAuthenticationManagerResolver authenticationManagerResolver = new JwtIssuerAuthenticationManagerResolver( + issuer); + MockHttpServletRequest request = new MockHttpServletRequest(); + request.addHeader("Authorization", "Bearer " + jws.serialize()); + assertThatExceptionOfType(IllegalArgumentException.class) + .isThrownBy(() -> authenticationManagerResolver.resolve(request)); + AuthenticationManager authenticationManager = authenticationManagerResolver.resolve(request); + assertThat(authenticationManager).isNotNull(); + } + } + @Test public void resolveWhenUsingUntrustedIssuerThenException() { JwtIssuerAuthenticationManagerResolver authenticationManagerResolver = diff --git a/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/authentication/JwtIssuerReactiveAuthenticationManagerResolverTests.java b/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/authentication/JwtIssuerReactiveAuthenticationManagerResolverTests.java index d694707096..4c1a4b176f 100644 --- a/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/authentication/JwtIssuerReactiveAuthenticationManagerResolverTests.java +++ b/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/authentication/JwtIssuerReactiveAuthenticationManagerResolverTests.java @@ -40,9 +40,11 @@ import org.springframework.security.authentication.ReactiveAuthenticationManager import org.springframework.security.authentication.ReactiveAuthenticationManagerResolver; import org.springframework.security.oauth2.core.OAuth2AuthenticationException; import org.springframework.security.oauth2.jose.TestKeys; +import org.springframework.security.oauth2.jwt.JwtClaimNames; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatCode; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.mockito.Mockito.mock; import static org.springframework.security.oauth2.jwt.JwtClaimNames.ISS; @@ -85,6 +87,31 @@ public class JwtIssuerReactiveAuthenticationManagerResolverTests { } } + // gh-10444 + @Test + public void resolveWhenIssuerFailsThenErrorNotCached() throws Exception { + try (MockWebServer server = new MockWebServer()) { + String issuer = server.url("").toString(); + // @formatter:off + server.enqueue(new MockResponse().setResponseCode(500).setHeader("Content-Type", "application/json") + .setBody(String.format(DEFAULT_RESPONSE_TEMPLATE, issuer, issuer))); + server.enqueue(new MockResponse().setResponseCode(200).setHeader("Content-Type", "application/json") + .setBody(String.format(DEFAULT_RESPONSE_TEMPLATE, issuer, issuer))); + // @formatter:on + JWSObject jws = new JWSObject(new JWSHeader(JWSAlgorithm.RS256), + new Payload(new JSONObject(Collections.singletonMap(JwtClaimNames.ISS, issuer)))); + jws.sign(new RSASSASigner(TestKeys.DEFAULT_PRIVATE_KEY)); + JwtIssuerReactiveAuthenticationManagerResolver authenticationManagerResolver = new JwtIssuerReactiveAuthenticationManagerResolver( + issuer); + MockServerWebExchange exchange = withBearerToken(jws.serialize()); + assertThatExceptionOfType(IllegalArgumentException.class) + .isThrownBy(() -> authenticationManagerResolver.resolve(exchange).block()); + ReactiveAuthenticationManager authenticationManager = authenticationManagerResolver.resolve(exchange) + .block(); + assertThat(authenticationManager).isNotNull(); + } + } + @Test public void resolveWhenUsingUntrustedIssuerThenException() { JwtIssuerReactiveAuthenticationManagerResolver authenticationManagerResolver =