From af669a216655c6cc8dc2aab9fe6a0d3d3e416da6 Mon Sep 17 00:00:00 2001 From: Josh Cummings Date: Mon, 2 Nov 2020 17:29:24 -0700 Subject: [PATCH] Remove Reliance on BearerTokenResolver Closes gh-9186 --- ...wtIssuerAuthenticationManagerResolver.java | 70 ++++++------ ...ReactiveAuthenticationManagerResolver.java | 88 +++++++------- ...uerAuthenticationManagerResolverTests.java | 104 ++++++++--------- ...iveAuthenticationManagerResolverTests.java | 108 ++++++++++-------- 4 files changed, 182 insertions(+), 188 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 f324d84f4d..56b8df7bc3 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 @@ -34,12 +34,13 @@ import org.springframework.core.log.LogMessage; import org.springframework.lang.NonNull; 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.JwtDecoder; import org.springframework.security.oauth2.jwt.JwtDecoders; +import org.springframework.security.oauth2.server.resource.BearerTokenAuthenticationToken; import org.springframework.security.oauth2.server.resource.InvalidBearerTokenException; -import org.springframework.security.oauth2.server.resource.web.BearerTokenResolver; -import org.springframework.security.oauth2.server.resource.web.DefaultBearerTokenResolver; import org.springframework.util.Assert; /** @@ -63,9 +64,7 @@ import org.springframework.util.Assert; */ public final class JwtIssuerAuthenticationManagerResolver implements AuthenticationManagerResolver { - private final AuthenticationManagerResolver issuerAuthenticationManagerResolver; - - private Converter issuerConverter = new JwtClaimIssuerConverter(); + private final AuthenticationManager authenticationManager; /** * Construct a {@link JwtIssuerAuthenticationManagerResolver} using the provided @@ -83,8 +82,9 @@ public final class JwtIssuerAuthenticationManagerResolver implements Authenticat */ public JwtIssuerAuthenticationManagerResolver(Collection trustedIssuers) { Assert.notEmpty(trustedIssuers, "trustedIssuers cannot be empty"); - this.issuerAuthenticationManagerResolver = new TrustedIssuerJwtAuthenticationManagerResolver( - Collections.unmodifiableCollection(trustedIssuers)::contains); + this.authenticationManager = new ResolvingAuthenticationManager( + new TrustedIssuerJwtAuthenticationManagerResolver( + Collections.unmodifiableCollection(trustedIssuers)::contains)); } /** @@ -111,7 +111,7 @@ public final class JwtIssuerAuthenticationManagerResolver implements Authenticat public JwtIssuerAuthenticationManagerResolver( AuthenticationManagerResolver issuerAuthenticationManagerResolver) { Assert.notNull(issuerAuthenticationManagerResolver, "issuerAuthenticationManagerResolver cannot be null"); - this.issuerAuthenticationManagerResolver = issuerAuthenticationManagerResolver; + this.authenticationManager = new ResolvingAuthenticationManager(issuerAuthenticationManagerResolver); } /** @@ -122,40 +122,39 @@ public final class JwtIssuerAuthenticationManagerResolver implements Authenticat */ @Override public AuthenticationManager resolve(HttpServletRequest request) { - String issuer = this.issuerConverter.convert(request); - AuthenticationManager authenticationManager = this.issuerAuthenticationManagerResolver.resolve(issuer); - if (authenticationManager == null) { - throw new InvalidBearerTokenException("Invalid issuer"); - } - return authenticationManager; + return this.authenticationManager; } - /** - * Set a custom bearer token resolver - * - * @since 5.5 - */ - public void setBearerTokenResolver(BearerTokenResolver bearerTokenResolver) { - Assert.notNull(bearerTokenResolver, "bearerTokenResolver cannot be null"); - this.issuerConverter = new JwtClaimIssuerConverter(bearerTokenResolver); - } + private static class ResolvingAuthenticationManager implements AuthenticationManager { - private static class JwtClaimIssuerConverter implements Converter { + private final Converter issuerConverter = new JwtClaimIssuerConverter(); - private final BearerTokenResolver resolver; + private final AuthenticationManagerResolver issuerAuthenticationManagerResolver; - JwtClaimIssuerConverter() { - this(new DefaultBearerTokenResolver()); - } - - JwtClaimIssuerConverter(BearerTokenResolver bearerTokenResolver) { - Assert.notNull(bearerTokenResolver, "bearerTokenResolver cannot be null"); - this.resolver = bearerTokenResolver; + ResolvingAuthenticationManager(AuthenticationManagerResolver issuerAuthenticationManagerResolver) { + this.issuerAuthenticationManagerResolver = issuerAuthenticationManagerResolver; } @Override - public String convert(@NonNull HttpServletRequest request) { - String token = this.resolver.resolve(request); + public Authentication authenticate(Authentication authentication) throws AuthenticationException { + Assert.isTrue(authentication instanceof BearerTokenAuthenticationToken, + "Authentication must be of type BearerTokenAuthenticationToken"); + BearerTokenAuthenticationToken token = (BearerTokenAuthenticationToken) authentication; + String issuer = this.issuerConverter.convert(token); + AuthenticationManager authenticationManager = this.issuerAuthenticationManagerResolver.resolve(issuer); + if (authenticationManager == null) { + throw new InvalidBearerTokenException("Invalid issuer"); + } + return authenticationManager.authenticate(authentication); + } + + } + + private static class JwtClaimIssuerConverter implements Converter { + + @Override + public String convert(@NonNull BearerTokenAuthenticationToken authentication) { + String token = authentication.getToken(); try { String issuer = JWTParser.parse(token).getJWTClaimsSet().getIssuer(); if (issuer != null) { @@ -170,8 +169,7 @@ public final class JwtIssuerAuthenticationManagerResolver implements Authenticat } - private static class TrustedIssuerJwtAuthenticationManagerResolver - implements AuthenticationManagerResolver { + static class TrustedIssuerJwtAuthenticationManagerResolver implements AuthenticationManagerResolver { private final Log logger = LogFactory.getLog(getClass()); 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 2db203df60..5a08d51d94 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 @@ -32,11 +32,11 @@ import org.springframework.lang.NonNull; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.ReactiveAuthenticationManager; import org.springframework.security.authentication.ReactiveAuthenticationManagerResolver; +import org.springframework.security.core.Authentication; import org.springframework.security.oauth2.core.OAuth2AuthenticationException; import org.springframework.security.oauth2.jwt.ReactiveJwtDecoders; import org.springframework.security.oauth2.server.resource.BearerTokenAuthenticationToken; import org.springframework.security.oauth2.server.resource.InvalidBearerTokenException; -import org.springframework.security.oauth2.server.resource.web.server.ServerBearerTokenAuthenticationConverter; import org.springframework.util.Assert; import org.springframework.web.server.ServerWebExchange; @@ -63,9 +63,7 @@ import org.springframework.web.server.ServerWebExchange; public final class JwtIssuerReactiveAuthenticationManagerResolver implements ReactiveAuthenticationManagerResolver { - private final ReactiveAuthenticationManagerResolver issuerAuthenticationManagerResolver; - - private Converter> issuerConverter = new JwtClaimIssuerConverter(); + private final ReactiveAuthenticationManager authenticationManager; /** * Construct a {@link JwtIssuerReactiveAuthenticationManagerResolver} using the @@ -83,8 +81,8 @@ public final class JwtIssuerReactiveAuthenticationManagerResolver */ public JwtIssuerReactiveAuthenticationManagerResolver(Collection trustedIssuers) { Assert.notEmpty(trustedIssuers, "trustedIssuers cannot be empty"); - this.issuerAuthenticationManagerResolver = new TrustedIssuerJwtAuthenticationManagerResolver( - new ArrayList<>(trustedIssuers)::contains); + this.authenticationManager = new ResolvingAuthenticationManager( + new TrustedIssuerJwtAuthenticationManagerResolver(new ArrayList<>(trustedIssuers)::contains)); } /** @@ -111,7 +109,7 @@ public final class JwtIssuerReactiveAuthenticationManagerResolver public JwtIssuerReactiveAuthenticationManagerResolver( ReactiveAuthenticationManagerResolver issuerAuthenticationManagerResolver) { Assert.notNull(issuerAuthenticationManagerResolver, "issuerAuthenticationManagerResolver cannot be null"); - this.issuerAuthenticationManagerResolver = issuerAuthenticationManagerResolver; + this.authenticationManager = new ResolvingAuthenticationManager(issuerAuthenticationManagerResolver); } /** @@ -122,61 +120,53 @@ public final class JwtIssuerReactiveAuthenticationManagerResolver */ @Override public Mono resolve(ServerWebExchange exchange) { - // @formatter:off - return this.issuerConverter.convert(exchange) - .flatMap((issuer) -> this.issuerAuthenticationManagerResolver - .resolve(issuer) - .switchIfEmpty(Mono.error(() -> new InvalidBearerTokenException("Invalid issuer " + issuer))) - ); - // @formatter:on + return Mono.just(this.authenticationManager); } - /** - * Set a custom server bearer token authentication converter - * - * @since 5.5 - */ - public void setServerBearerTokenAuthenticationConverter( - ServerBearerTokenAuthenticationConverter serverBearerTokenAuthenticationConverter) { - Assert.notNull(serverBearerTokenAuthenticationConverter, - "serverBearerTokenAuthenticationConverter cannot be null"); - this.issuerConverter = new JwtClaimIssuerConverter(serverBearerTokenAuthenticationConverter); - } + private static class ResolvingAuthenticationManager implements ReactiveAuthenticationManager { - private static class JwtClaimIssuerConverter implements Converter> { + private final Converter> issuerConverter = new JwtClaimIssuerConverter(); - private final ServerBearerTokenAuthenticationConverter converter; + private final ReactiveAuthenticationManagerResolver issuerAuthenticationManagerResolver; - JwtClaimIssuerConverter() { - this(new ServerBearerTokenAuthenticationConverter()); - } + ResolvingAuthenticationManager( + ReactiveAuthenticationManagerResolver issuerAuthenticationManagerResolver) { - JwtClaimIssuerConverter(ServerBearerTokenAuthenticationConverter serverBearerTokenAuthenticationConverter) { - Assert.notNull(serverBearerTokenAuthenticationConverter, - "serverBearerTokenAuthenticationConverter cannot be null"); - this.converter = serverBearerTokenAuthenticationConverter; + this.issuerAuthenticationManagerResolver = issuerAuthenticationManagerResolver; } @Override - public Mono convert(@NonNull ServerWebExchange exchange) { - return this.converter.convert(exchange).map((convertedToken) -> { - BearerTokenAuthenticationToken token = (BearerTokenAuthenticationToken) convertedToken; - try { - String issuer = JWTParser.parse(token.getToken()).getJWTClaimsSet().getIssuer(); - if (issuer == null) { - throw new InvalidBearerTokenException("Missing issuer"); - } - return issuer; - } - catch (Exception ex) { - throw new InvalidBearerTokenException(ex.getMessage(), ex); - } - }); + public Mono authenticate(Authentication authentication) { + Assert.isTrue(authentication instanceof BearerTokenAuthenticationToken, + "Authentication must be of type BearerTokenAuthenticationToken"); + BearerTokenAuthenticationToken token = (BearerTokenAuthenticationToken) authentication; + return this.issuerConverter.convert(token) + .flatMap((issuer) -> this.issuerAuthenticationManagerResolver.resolve(issuer).switchIfEmpty( + Mono.error(() -> new InvalidBearerTokenException("Invalid issuer " + issuer)))) + .flatMap((manager) -> manager.authenticate(authentication)); } } - private static class TrustedIssuerJwtAuthenticationManagerResolver + private static class JwtClaimIssuerConverter implements Converter> { + + @Override + public Mono convert(@NonNull BearerTokenAuthenticationToken token) { + try { + String issuer = JWTParser.parse(token.getToken()).getJWTClaimsSet().getIssuer(); + if (issuer == null) { + throw new InvalidBearerTokenException("Missing issuer"); + } + return Mono.just(issuer); + } + catch (Exception ex) { + return Mono.error(() -> new InvalidBearerTokenException(ex.getMessage(), ex)); + } + } + + } + + static class TrustedIssuerJwtAuthenticationManagerResolver implements ReactiveAuthenticationManagerResolver { private final Map> authenticationManagers = new ConcurrentHashMap<>(); 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 3fd3d3f498..f99fcb1248 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 @@ -21,8 +21,6 @@ import java.util.Collections; import java.util.HashMap; import java.util.Map; -import javax.servlet.http.HttpServletRequest; - import com.nimbusds.jose.JWSAlgorithm; import com.nimbusds.jose.JWSHeader; import com.nimbusds.jose.JWSObject; @@ -35,21 +33,20 @@ import okhttp3.mockwebserver.MockResponse; import okhttp3.mockwebserver.MockWebServer; import org.junit.Test; -import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.AuthenticationManagerResolver; +import org.springframework.security.core.Authentication; import org.springframework.security.oauth2.core.OAuth2AuthenticationException; import org.springframework.security.oauth2.jose.TestKeys; import org.springframework.security.oauth2.jwt.JwtClaimNames; -import org.springframework.security.oauth2.server.resource.web.BearerTokenResolver; +import org.springframework.security.oauth2.server.resource.BearerTokenAuthenticationToken; +import org.springframework.security.oauth2.server.resource.authentication.JwtIssuerAuthenticationManagerResolver.TrustedIssuerJwtAuthenticationManagerResolver; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; -import static org.mockito.Mockito.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.verify; +import static org.mockito.BDDMockito.mock; +import static org.mockito.BDDMockito.verify; /** * Tests for {@link JwtIssuerAuthenticationManagerResolver} @@ -59,7 +56,7 @@ public class JwtIssuerAuthenticationManagerResolverTests { private static final String DEFAULT_RESPONSE_TEMPLATE = "{\n" + " \"issuer\": \"%s\", \n" + " \"jwks_uri\": \"%s/.well-known/jwks.json\" \n" + "}"; - private static final String JWK_SET = "{\"keys\":[{\"kty\":\"RSA\",\"e\":\"AQAB\",\"use\":\"sig\",\"kid\":\"one\",\"n\":\"oXJ8OyOv_eRnce4akdanR4KYRfnC2zLV4uYNQpcFn6oHL0dj7D6kxQmsXoYgJV8ZVDn71KGmuLvolxsDncc2UrhyMBY6DVQVgMSVYaPCTgW76iYEKGgzTEw5IBRQL9w3SRJWd3VJTZZQjkXef48Ocz06PGF3lhbz4t5UEZtdF4rIe7u-977QwHuh7yRPBQ3sII-cVoOUMgaXB9SHcGF2iZCtPzL_IffDUcfhLQteGebhW8A6eUHgpD5A1PQ-JCw_G7UOzZAjjDjtNM2eqm8j-Ms_gqnm4MiCZ4E-9pDN77CAAPVN7kuX6ejs9KBXpk01z48i9fORYk9u7rAkh1HuQw\"}]}"; + private static final String JWK_SET = "{\"keys\":[{\"kty\":\"RSA\",\"e\":\"AQAB\",\"use\":\"sig\",\"kid\":\"one\",\"n\":\"3FlqJr5TRskIQIgdE3Dd7D9lboWdcTUT8a-fJR7MAvQm7XXNoYkm3v7MQL1NYtDvL2l8CAnc0WdSTINU6IRvc5Kqo2Q4csNX9SHOmEfzoROjQqahEcve1jBXluoCXdYuYpx4_1tfRgG6ii4Uhxh6iI8qNMJQX-fLfqhbfYfxBQVRPywBkAbIP4x1EAsbC6FSNmkhCxiMNqEgxaIpY8C2kJdJ_ZIV-WW4noDdzpKqHcwmB8FsrumlVY_DNVvUSDIipiq9PbP4H99TXN1o746oRaNa07rq1hoCgMSSy-85SagCoxlmyE-D-of9SsMY8Ol9t0rdzpobBuhyJ_o5dfvjKw\"}]}"; private String jwt = jwt("iss", "trusted"); @@ -81,17 +78,36 @@ public class JwtIssuerAuthenticationManagerResolverTests { .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 = new JwtIssuerAuthenticationManagerResolver( issuer); - MockHttpServletRequest request = new MockHttpServletRequest(); - request.addHeader("Authorization", "Bearer " + jws.serialize()); - AuthenticationManager authenticationManager = authenticationManagerResolver.resolve(request); + Authentication token = withBearerToken(jws.serialize()); + AuthenticationManager authenticationManager = authenticationManagerResolver.resolve(null); assertThat(authenticationManager).isNotNull(); - AuthenticationManager cachedAuthenticationManager = authenticationManagerResolver.resolve(request); + Authentication authentication = authenticationManager.authenticate(token); + assertThat(authentication.isAuthenticated()).isTrue(); + } + } + + @Test + public void resolveWhenUsingSameIssuerThenReturnsSameAuthenticationManager() throws Exception { + try (MockWebServer server = new MockWebServer()) { + String issuer = server.url("").toString(); + 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)); + TrustedIssuerJwtAuthenticationManagerResolver resolver = new TrustedIssuerJwtAuthenticationManagerResolver( + (iss) -> iss.equals(issuer)); + AuthenticationManager authenticationManager = resolver.resolve(issuer); + AuthenticationManager cachedAuthenticationManager = resolver.resolve(issuer); assertThat(authenticationManager).isSameAs(cachedAuthenticationManager); } } @@ -100,11 +116,10 @@ public class JwtIssuerAuthenticationManagerResolverTests { public void resolveWhenUsingUntrustedIssuerThenException() { JwtIssuerAuthenticationManagerResolver authenticationManagerResolver = new JwtIssuerAuthenticationManagerResolver( "other", "issuers"); - MockHttpServletRequest request = new MockHttpServletRequest(); - request.addHeader("Authorization", "Bearer " + this.jwt); + Authentication token = withBearerToken(this.jwt); // @formatter:off assertThatExceptionOfType(OAuth2AuthenticationException.class) - .isThrownBy(() -> authenticationManagerResolver.resolve(request)) + .isThrownBy(() -> authenticationManagerResolver.resolve(null).authenticate(token)) .withMessageContaining("Invalid issuer"); // @formatter:on } @@ -114,43 +129,30 @@ public class JwtIssuerAuthenticationManagerResolverTests { AuthenticationManager authenticationManager = mock(AuthenticationManager.class); JwtIssuerAuthenticationManagerResolver authenticationManagerResolver = new JwtIssuerAuthenticationManagerResolver( (issuer) -> authenticationManager); - MockHttpServletRequest request = new MockHttpServletRequest(); - request.addHeader("Authorization", "Bearer " + this.jwt); - assertThat(authenticationManagerResolver.resolve(request)).isSameAs(authenticationManager); - } - - @Test - public void resolveWhenUsingCustomIssuerAuthenticationManagerResolverAndCustomBearerTokenResolverThenUses() { - AuthenticationManager authenticationManager = mock(AuthenticationManager.class); - JwtIssuerAuthenticationManagerResolver authenticationManagerResolver = new JwtIssuerAuthenticationManagerResolver( - (issuer) -> authenticationManager); - BearerTokenResolver bearerTokenResolverSpy = spy(new TestBearerTokenResolver()); - authenticationManagerResolver.setBearerTokenResolver(bearerTokenResolverSpy); - MockHttpServletRequest request = new MockHttpServletRequest(); - request.addHeader("Authorization", "Bearer " + this.jwt); - assertThat(authenticationManagerResolver.resolve(request)).isSameAs(authenticationManager); - verify(bearerTokenResolverSpy).resolve(any()); + Authentication token = withBearerToken(this.jwt); + authenticationManagerResolver.resolve(null).authenticate(token); + verify(authenticationManager).authenticate(token); } @Test public void resolveWhenUsingExternalSourceThenRespondsToChanges() { - MockHttpServletRequest request = new MockHttpServletRequest(); - request.addHeader("Authorization", "Bearer " + this.jwt); + Authentication token = withBearerToken(this.jwt); Map authenticationManagers = new HashMap<>(); JwtIssuerAuthenticationManagerResolver authenticationManagerResolver = new JwtIssuerAuthenticationManagerResolver( authenticationManagers::get); // @formatter:off assertThatExceptionOfType(OAuth2AuthenticationException.class) - .isThrownBy(() -> authenticationManagerResolver.resolve(request)) + .isThrownBy(() -> authenticationManagerResolver.resolve(null).authenticate(token)) .withMessageContaining("Invalid issuer"); // @formatter:on AuthenticationManager authenticationManager = mock(AuthenticationManager.class); authenticationManagers.put("trusted", authenticationManager); - assertThat(authenticationManagerResolver.resolve(request)).isSameAs(authenticationManager); + authenticationManagerResolver.resolve(null).authenticate(token); + verify(authenticationManager).authenticate(token); authenticationManagers.clear(); // @formatter:off assertThatExceptionOfType(OAuth2AuthenticationException.class) - .isThrownBy(() -> authenticationManagerResolver.resolve(request)) + .isThrownBy(() -> authenticationManagerResolver.resolve(null).authenticate(token)) .withMessageContaining("Invalid issuer"); // @formatter:on } @@ -159,11 +161,10 @@ public class JwtIssuerAuthenticationManagerResolverTests { public void resolveWhenBearerTokenMalformedThenException() { JwtIssuerAuthenticationManagerResolver authenticationManagerResolver = new JwtIssuerAuthenticationManagerResolver( "trusted"); - MockHttpServletRequest request = new MockHttpServletRequest(); - request.addHeader("Authorization", "Bearer jwt"); + Authentication token = withBearerToken("jwt"); // @formatter:off assertThatExceptionOfType(OAuth2AuthenticationException.class) - .isThrownBy(() -> authenticationManagerResolver.resolve(request)) + .isThrownBy(() -> authenticationManagerResolver.resolve(null).authenticate(token)) .withMessageNotContaining("Invalid issuer"); // @formatter:on } @@ -172,11 +173,10 @@ public class JwtIssuerAuthenticationManagerResolverTests { public void resolveWhenBearerTokenNoIssuerThenException() { JwtIssuerAuthenticationManagerResolver authenticationManagerResolver = new JwtIssuerAuthenticationManagerResolver( "trusted"); - MockHttpServletRequest request = new MockHttpServletRequest(); - request.addHeader("Authorization", "Bearer " + this.noIssuer); + Authentication token = withBearerToken(this.noIssuer); // @formatter:off assertThatExceptionOfType(OAuth2AuthenticationException.class) - .isThrownBy(() -> authenticationManagerResolver.resolve(request)) + .isThrownBy(() -> authenticationManagerResolver.resolve(null).authenticate(token)) .withMessageContaining("Missing issuer"); // @formatter:on } @@ -185,12 +185,11 @@ public class JwtIssuerAuthenticationManagerResolverTests { public void resolveWhenBearerTokenEvilThenGenericException() { JwtIssuerAuthenticationManagerResolver authenticationManagerResolver = new JwtIssuerAuthenticationManagerResolver( "trusted"); - MockHttpServletRequest request = new MockHttpServletRequest(); - request.addHeader("Authorization", "Bearer " + this.evil); + Authentication token = withBearerToken(this.evil); // @formatter:off assertThatExceptionOfType(OAuth2AuthenticationException.class) .isThrownBy(() -> authenticationManagerResolver - .resolve(request) + .resolve(null).authenticate(token) ) .withMessage("Invalid issuer"); // @formatter:on @@ -210,18 +209,13 @@ public class JwtIssuerAuthenticationManagerResolverTests { .isThrownBy(() -> new JwtIssuerAuthenticationManagerResolver((AuthenticationManagerResolver) null)); } + private Authentication withBearerToken(String token) { + return new BearerTokenAuthenticationToken(token); + } + private String jwt(String claim, String value) { PlainJWT jwt = new PlainJWT(new JWTClaimsSet.Builder().claim(claim, value).build()); return jwt.serialize(); } - static class TestBearerTokenResolver implements BearerTokenResolver { - - @Override - public String resolve(HttpServletRequest request) { - return "eyJhbGciOiJub25lIn0.eyJpc3MiOiJ0cnVzdGVkIn0."; - } - - } - } 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 278096aaed..f691c36aa6 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 @@ -34,22 +34,22 @@ import okhttp3.mockwebserver.MockWebServer; import org.junit.Test; import reactor.core.publisher.Mono; -import org.springframework.mock.http.server.reactive.MockServerHttpRequest; -import org.springframework.mock.web.server.MockServerWebExchange; import org.springframework.security.authentication.ReactiveAuthenticationManager; import org.springframework.security.authentication.ReactiveAuthenticationManagerResolver; +import org.springframework.security.core.Authentication; import org.springframework.security.oauth2.core.OAuth2AuthenticationException; import org.springframework.security.oauth2.jose.TestKeys; import org.springframework.security.oauth2.jwt.JwtClaimNames; -import org.springframework.security.oauth2.server.resource.web.server.ServerBearerTokenAuthenticationConverter; +import org.springframework.security.oauth2.server.resource.BearerTokenAuthenticationToken; +import org.springframework.security.oauth2.server.resource.authentication.JwtIssuerReactiveAuthenticationManagerResolver.TrustedIssuerJwtAuthenticationManagerResolver; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; -import static org.mockito.Mockito.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.verify; +import static org.mockito.BDDMockito.any; +import static org.mockito.BDDMockito.mock; +import static org.mockito.BDDMockito.verify; +import static org.mockito.BDDMockito.when; /** * Tests for {@link JwtIssuerReactiveAuthenticationManagerResolver} @@ -63,7 +63,7 @@ public class JwtIssuerReactiveAuthenticationManagerResolverTests { + "}"; // @formatter:on - private static final String JWK_SET = "{\"keys\":[{\"kty\":\"RSA\",\"e\":\"AQAB\",\"use\":\"sig\",\"kid\":\"one\",\"n\":\"oXJ8OyOv_eRnce4akdanR4KYRfnC2zLV4uYNQpcFn6oHL0dj7D6kxQmsXoYgJV8ZVDn71KGmuLvolxsDncc2UrhyMBY6DVQVgMSVYaPCTgW76iYEKGgzTEw5IBRQL9w3SRJWd3VJTZZQjkXef48Ocz06PGF3lhbz4t5UEZtdF4rIe7u-977QwHuh7yRPBQ3sII-cVoOUMgaXB9SHcGF2iZCtPzL_IffDUcfhLQteGebhW8A6eUHgpD5A1PQ-JCw_G7UOzZAjjDjtNM2eqm8j-Ms_gqnm4MiCZ4E-9pDN77CAAPVN7kuX6ejs9KBXpk01z48i9fORYk9u7rAkh1HuQw\"}]}"; + private static final String JWK_SET = "{\"keys\":[{\"kty\":\"RSA\",\"e\":\"AQAB\",\"use\":\"sig\",\"kid\":\"one\",\"n\":\"3FlqJr5TRskIQIgdE3Dd7D9lboWdcTUT8a-fJR7MAvQm7XXNoYkm3v7MQL1NYtDvL2l8CAnc0WdSTINU6IRvc5Kqo2Q4csNX9SHOmEfzoROjQqahEcve1jBXluoCXdYuYpx4_1tfRgG6ii4Uhxh6iI8qNMJQX-fLfqhbfYfxBQVRPywBkAbIP4x1EAsbC6FSNmkhCxiMNqEgxaIpY8C2kJdJ_ZIV-WW4noDdzpKqHcwmB8FsrumlVY_DNVvUSDIipiq9PbP4H99TXN1o746oRaNa07rq1hoCgMSSy-85SagCoxlmyE-D-of9SsMY8Ol9t0rdzpobBuhyJ_o5dfvjKw\"}]}"; private String jwt = jwt("iss", "trusted"); @@ -79,17 +79,34 @@ public class JwtIssuerReactiveAuthenticationManagerResolverTests { .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)); 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()); - ReactiveAuthenticationManager authenticationManager = authenticationManagerResolver.resolve(exchange) - .block(); + ReactiveAuthenticationManager authenticationManager = authenticationManagerResolver.resolve(null).block(); assertThat(authenticationManager).isNotNull(); - ReactiveAuthenticationManager cachedAuthenticationManager = authenticationManagerResolver.resolve(exchange) - .block(); + BearerTokenAuthenticationToken token = withBearerToken(jws.serialize()); + Authentication authentication = authenticationManager.authenticate(token).block(); + assertThat(authentication).isNotNull(); + assertThat(authentication.isAuthenticated()).isTrue(); + } + } + + @Test + public void resolveWhenUsingSameIssuerThenReturnsSameAuthenticationManager() throws Exception { + try (MockWebServer server = new MockWebServer()) { + String issuer = server.url("").toString(); + 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)); + TrustedIssuerJwtAuthenticationManagerResolver resolver = new TrustedIssuerJwtAuthenticationManagerResolver( + (iss) -> iss.equals(issuer)); + ReactiveAuthenticationManager authenticationManager = resolver.resolve(issuer).block(); + ReactiveAuthenticationManager cachedAuthenticationManager = resolver.resolve(issuer).block(); assertThat(authenticationManager).isSameAs(cachedAuthenticationManager); } } @@ -98,53 +115,48 @@ public class JwtIssuerReactiveAuthenticationManagerResolverTests { public void resolveWhenUsingUntrustedIssuerThenException() { JwtIssuerReactiveAuthenticationManagerResolver authenticationManagerResolver = new JwtIssuerReactiveAuthenticationManagerResolver( "other", "issuers"); - MockServerWebExchange exchange = withBearerToken(this.jwt); + Authentication token = withBearerToken(this.jwt); // @formatter:off assertThatExceptionOfType(OAuth2AuthenticationException.class) - .isThrownBy(() -> authenticationManagerResolver.resolve(exchange).block()) + .isThrownBy(() -> authenticationManagerResolver.resolve(null) + .flatMap((authenticationManager) -> authenticationManager.authenticate(token)) + .block()) .withMessageContaining("Invalid issuer"); // @formatter:on } @Test public void resolveWhenUsingCustomIssuerAuthenticationManagerResolverThenUses() { + Authentication token = withBearerToken(this.jwt); ReactiveAuthenticationManager authenticationManager = mock(ReactiveAuthenticationManager.class); + when(authenticationManager.authenticate(token)).thenReturn(Mono.empty()); JwtIssuerReactiveAuthenticationManagerResolver authenticationManagerResolver = new JwtIssuerReactiveAuthenticationManagerResolver( (issuer) -> Mono.just(authenticationManager)); - MockServerWebExchange exchange = withBearerToken(this.jwt); - assertThat(authenticationManagerResolver.resolve(exchange).block()).isSameAs(authenticationManager); - } - - @Test - public void resolveWhenUsingCustomIssuerAuthenticationManagerResolverAndCustomServerBearerTokenAuthenticationConverterThenUses() { - ReactiveAuthenticationManager authenticationManager = mock(ReactiveAuthenticationManager.class); - JwtIssuerReactiveAuthenticationManagerResolver authenticationManagerResolver = new JwtIssuerReactiveAuthenticationManagerResolver( - (issuer) -> Mono.just(authenticationManager)); - ServerBearerTokenAuthenticationConverter serverBearerTokenAuthenticationConverterSpy = spy( - new ServerBearerTokenAuthenticationConverter()); - authenticationManagerResolver - .setServerBearerTokenAuthenticationConverter(serverBearerTokenAuthenticationConverterSpy); - MockServerWebExchange exchange = withBearerToken(this.jwt); - assertThat(authenticationManagerResolver.resolve(exchange).block()).isSameAs(authenticationManager); - verify(serverBearerTokenAuthenticationConverterSpy).convert(any()); + authenticationManagerResolver.resolve(null).flatMap((manager) -> manager.authenticate(token)).block(); + verify(authenticationManager).authenticate(any()); } @Test public void resolveWhenUsingExternalSourceThenRespondsToChanges() { - MockServerWebExchange exchange = withBearerToken(this.jwt); + Authentication token = withBearerToken(this.jwt); Map authenticationManagers = new HashMap<>(); JwtIssuerReactiveAuthenticationManagerResolver authenticationManagerResolver = new JwtIssuerReactiveAuthenticationManagerResolver( (issuer) -> Mono.justOrEmpty(authenticationManagers.get(issuer))); assertThatExceptionOfType(OAuth2AuthenticationException.class) - .isThrownBy(() -> authenticationManagerResolver.resolve(exchange).block()) + .isThrownBy(() -> authenticationManagerResolver.resolve(null) + .flatMap((manager) -> manager.authenticate(token)).block()) .withMessageContaining("Invalid issuer"); ReactiveAuthenticationManager authenticationManager = mock(ReactiveAuthenticationManager.class); + when(authenticationManager.authenticate(token)).thenReturn(Mono.empty()); authenticationManagers.put("trusted", authenticationManager); - assertThat(authenticationManagerResolver.resolve(exchange).block()).isSameAs(authenticationManager); + authenticationManagerResolver.resolve(null).flatMap((manager) -> manager.authenticate(token)).block(); + verify(authenticationManager).authenticate(token); authenticationManagers.clear(); // @formatter:off assertThatExceptionOfType(OAuth2AuthenticationException.class) - .isThrownBy(() -> authenticationManagerResolver.resolve(exchange).block()) + .isThrownBy(() -> authenticationManagerResolver.resolve(null) + .flatMap((manager) -> manager.authenticate(token)) + .block()) .withMessageContaining("Invalid issuer"); // @formatter:on } @@ -153,10 +165,12 @@ public class JwtIssuerReactiveAuthenticationManagerResolverTests { public void resolveWhenBearerTokenMalformedThenException() { JwtIssuerReactiveAuthenticationManagerResolver authenticationManagerResolver = new JwtIssuerReactiveAuthenticationManagerResolver( "trusted"); - MockServerWebExchange exchange = withBearerToken("jwt"); + Authentication token = withBearerToken("jwt"); // @formatter:off assertThatExceptionOfType(OAuth2AuthenticationException.class) - .isThrownBy(() -> authenticationManagerResolver.resolve(exchange).block()) + .isThrownBy(() -> authenticationManagerResolver.resolve(null) + .flatMap((manager) -> manager.authenticate(token)) + .block()) .withMessageNotContaining("Invalid issuer"); // @formatter:on } @@ -165,9 +179,10 @@ public class JwtIssuerReactiveAuthenticationManagerResolverTests { public void resolveWhenBearerTokenNoIssuerThenException() { JwtIssuerReactiveAuthenticationManagerResolver authenticationManagerResolver = new JwtIssuerReactiveAuthenticationManagerResolver( "trusted"); - MockServerWebExchange exchange = withBearerToken(this.noIssuer); + Authentication token = withBearerToken(this.noIssuer); assertThatExceptionOfType(OAuth2AuthenticationException.class) - .isThrownBy(() -> authenticationManagerResolver.resolve(exchange).block()) + .isThrownBy(() -> authenticationManagerResolver.resolve(null) + .flatMap((manager) -> manager.authenticate(token)).block()) .withMessageContaining("Missing issuer"); } @@ -175,10 +190,12 @@ public class JwtIssuerReactiveAuthenticationManagerResolverTests { public void resolveWhenBearerTokenEvilThenGenericException() { JwtIssuerReactiveAuthenticationManagerResolver authenticationManagerResolver = new JwtIssuerReactiveAuthenticationManagerResolver( "trusted"); - MockServerWebExchange exchange = withBearerToken(this.evil); + Authentication token = withBearerToken(this.evil); // @formatter:off assertThatExceptionOfType(OAuth2AuthenticationException.class) - .isThrownBy(() -> authenticationManagerResolver.resolve(exchange).block()) + .isThrownBy(() -> authenticationManagerResolver.resolve(null) + .flatMap((manager) -> manager.authenticate(token)) + .block()) .withMessage("Invalid token"); // @formatter:on } @@ -202,13 +219,8 @@ public class JwtIssuerReactiveAuthenticationManagerResolverTests { return jwt.serialize(); } - private MockServerWebExchange withBearerToken(String token) { - // @formatter:off - MockServerHttpRequest request = MockServerHttpRequest.get("/") - .header("Authorization", "Bearer " + token) - .build(); - // @formatter:on - return MockServerWebExchange.from(request); + private BearerTokenAuthenticationToken withBearerToken(String token) { + return new BearerTokenAuthenticationToken(token); } }