From 77ded9e543e4b4e20604f033b1b9f93ab08e3169 Mon Sep 17 00:00:00 2001 From: Jesse Sightler Date: Fri, 15 Aug 2025 23:04:54 -0400 Subject: [PATCH] Added the ability to pass in a parameter when using JwtIssuerAuthenticationManagerResolver --- ...wtIssuerAuthenticationManagerResolver.java | 51 ++++++++++++++++++- ...uerAuthenticationManagerResolverTests.java | 49 +++++++++++++++++- 2 files changed, 97 insertions(+), 3 deletions(-) diff --git a/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/authentication/JwtIssuerAuthenticationManagerResolver.java b/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/authentication/JwtIssuerAuthenticationManagerResolver.java index 720b9127db..b7a64d78a4 100644 --- a/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/authentication/JwtIssuerAuthenticationManagerResolver.java +++ b/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/authentication/JwtIssuerAuthenticationManagerResolver.java @@ -30,11 +30,13 @@ import org.springframework.core.convert.converter.Converter; import org.springframework.core.log.LogMessage; import org.springframework.lang.NonNull; +import org.springframework.security.authentication.AbstractAuthenticationToken; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.AuthenticationManagerResolver; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.oauth2.core.OAuth2AuthenticationException; +import org.springframework.security.oauth2.jwt.Jwt; import org.springframework.security.oauth2.jwt.JwtDecoder; import org.springframework.security.oauth2.jwt.JwtDecoders; import org.springframework.security.oauth2.server.resource.InvalidBearerTokenException; @@ -93,7 +95,40 @@ public static JwtIssuerAuthenticationManagerResolver fromTrustedIssuers(Collecti public static JwtIssuerAuthenticationManagerResolver fromTrustedIssuers(Predicate trustedIssuers) { Assert.notNull(trustedIssuers, "trustedIssuers cannot be null"); return new JwtIssuerAuthenticationManagerResolver( - new TrustedIssuerJwtAuthenticationManagerResolver(trustedIssuers)); + new TrustedIssuerJwtAuthenticationManagerResolver(null, trustedIssuers)); + } + + /** + * Construct a {@link JwtIssuerAuthenticationManagerResolver} using the provided + * parameters + * @param trustedIssuers an array of trusted issuers + * @since 6.2 + */ + public static JwtIssuerAuthenticationManagerResolver fromTrustedIssuers(Converter jwtAuthenticationConverter, String... trustedIssuers) { + return fromTrustedIssuers(jwtAuthenticationConverter, Set.of(trustedIssuers)); + } + + /** + * Construct a {@link JwtIssuerAuthenticationManagerResolver} using the provided + * parameters + * @param trustedIssuers a collection of trusted issuers + * @since 6.2 + */ + public static JwtIssuerAuthenticationManagerResolver fromTrustedIssuers(Converter jwtAuthenticationConverter, Collection trustedIssuers) { + Assert.notEmpty(trustedIssuers, "trustedIssuers cannot be empty"); + return fromTrustedIssuers(jwtAuthenticationConverter, Set.copyOf(trustedIssuers)::contains); + } + + /** + * Construct a {@link JwtIssuerAuthenticationManagerResolver} using the provided + * parameters + * @param trustedIssuers a predicate to validate issuers + * @since 6.2 + */ + public static JwtIssuerAuthenticationManagerResolver fromTrustedIssuers(Converter jwtAuthenticationConverter, Predicate trustedIssuers) { + Assert.notNull(trustedIssuers, "trustedIssuers cannot be null"); + return new JwtIssuerAuthenticationManagerResolver( + new TrustedIssuerJwtAuthenticationManagerResolver(jwtAuthenticationConverter, trustedIssuers)); } /** @@ -117,6 +152,7 @@ public static JwtIssuerAuthenticationManagerResolver fromTrustedIssuers(Predicat * {@link AuthenticationManager} by the issuer */ public JwtIssuerAuthenticationManagerResolver( + AuthenticationManagerResolver issuerAuthenticationManagerResolver) { Assert.notNull(issuerAuthenticationManagerResolver, "issuerAuthenticationManagerResolver cannot be null"); this.authenticationManager = new ResolvingAuthenticationManager(issuerAuthenticationManagerResolver); @@ -197,7 +233,14 @@ static class TrustedIssuerJwtAuthenticationManagerResolver implements Authentica private final Predicate trustedIssuer; + private final Converter jwtAuthenticationConverter; + TrustedIssuerJwtAuthenticationManagerResolver(Predicate trustedIssuer) { + this(null, trustedIssuer); + } + + TrustedIssuerJwtAuthenticationManagerResolver(Converter jwtAuthenticationConverter, Predicate trustedIssuer) { + this.jwtAuthenticationConverter = jwtAuthenticationConverter; this.trustedIssuer = trustedIssuer; } @@ -208,7 +251,11 @@ public AuthenticationManager resolve(String issuer) { (k) -> { this.logger.debug("Constructing AuthenticationManager"); JwtDecoder jwtDecoder = JwtDecoders.fromIssuerLocation(issuer); - return new JwtAuthenticationProvider(jwtDecoder)::authenticate; + JwtAuthenticationProvider provider = new JwtAuthenticationProvider(jwtDecoder); + if (jwtAuthenticationConverter != null) { + provider.setJwtAuthenticationConverter(jwtAuthenticationConverter); + } + return provider::authenticate; }); this.logger.debug(LogMessage.format("Resolved AuthenticationManager for issuer '%s'", issuer)); return authenticationManager; 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 334470f23a..74b01e1a69 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 @@ -16,7 +16,7 @@ package org.springframework.security.oauth2.server.resource.authentication; -import java.util.Collection; +import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.Map; @@ -29,17 +29,22 @@ import com.nimbusds.jose.crypto.RSASSASigner; import com.nimbusds.jwt.JWTClaimsSet; import com.nimbusds.jwt.PlainJWT; +import jakarta.servlet.http.HttpServletRequest; import net.minidev.json.JSONObject; import okhttp3.mockwebserver.MockResponse; import okhttp3.mockwebserver.MockWebServer; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.Test; +import org.springframework.core.convert.converter.Converter; +import org.springframework.security.authentication.AbstractAuthenticationToken; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.AuthenticationManagerResolver; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.oauth2.core.OAuth2AuthenticationException; import org.springframework.security.oauth2.jose.TestKeys; +import org.springframework.security.oauth2.jwt.Jwt; import org.springframework.security.oauth2.jwt.JwtClaimNames; import org.springframework.security.oauth2.server.resource.InvalidBearerTokenException; import org.springframework.security.oauth2.server.resource.authentication.JwtIssuerAuthenticationManagerResolver.TrustedIssuerJwtAuthenticationManagerResolver; @@ -51,6 +56,7 @@ import static org.mockito.BDDMockito.given; import static org.mockito.BDDMockito.mock; import static org.mockito.BDDMockito.verify; +import static org.mockito.Mockito.when; /** * Tests for {@link JwtIssuerAuthenticationManagerResolver} @@ -267,6 +273,47 @@ public void resolveWhenBearerTokenEvilThenGenericException() { // @formatter:on } + @Test + public void testFactoryMethodWithConverter() throws Exception { + Converter converter = new Converter() { + @Override + public @Nullable AbstractAuthenticationToken convert(Jwt source) { + JwtAuthenticationToken authenticationToken = new JwtAuthenticationToken(source, Collections.emptyList(), "test_translated_name"); + authenticationToken.setDetails("test_translated_details"); + return authenticationToken; + } + }; + try (MockWebServer server = new MockWebServer()) { + server.start(); + String issuer = server.url("").toString(); + // @formatter:off + server.enqueue(new MockResponse().setResponseCode(200) + .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(JWK_SET) + ); + server.enqueue(new MockResponse().setResponseCode(200) + .setHeader("Content-Type", "application/json") + .setBody(JWK_SET) + ); + // @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 = JwtIssuerAuthenticationManagerResolver + .fromTrustedIssuers(converter, issuer); + Authentication token = withBearerToken(jws.serialize()); + AuthenticationManager authenticationManager = authenticationManagerResolver.resolve(null); + assertThat(authenticationManager).isNotNull(); + Authentication authentication = authenticationManager.authenticate(token); + assertThat(authentication.isAuthenticated()).isTrue(); + assertThat(authentication.getDetails()).isEqualTo("test_translated_details"); + } + } + @Test public void resolveWhenAuthenticationExceptionThenAuthenticationRequestIsIncluded() { Authentication authentication = new BearerTokenAuthenticationToken(this.jwt);