Add JwtIssuerAuthenticationManagerResolver

Fixes gh-7724
This commit is contained in:
Josh Cummings
2019-12-11 17:53:56 -07:00
parent 09810b8df9
commit de87675f6d
4 changed files with 508 additions and 104 deletions

View File

@@ -28,10 +28,19 @@ import java.time.Instant;
import java.time.ZoneId;
import java.util.Base64;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;
import javax.annotation.PreDestroy;
import com.nimbusds.jose.JWSAlgorithm;
import com.nimbusds.jose.JWSHeader;
import com.nimbusds.jose.JWSObject;
import com.nimbusds.jose.Payload;
import com.nimbusds.jose.crypto.RSASSASigner;
import com.nimbusds.jose.jwk.JWKSet;
import com.nimbusds.jose.jwk.RSAKey;
import net.minidev.json.JSONObject;
import okhttp3.mockwebserver.MockResponse;
import okhttp3.mockwebserver.MockWebServer;
import org.hamcrest.core.AllOf;
@@ -82,14 +91,15 @@ import org.springframework.security.oauth2.core.DefaultOAuth2AuthenticatedPrinci
import org.springframework.security.oauth2.core.OAuth2Error;
import org.springframework.security.oauth2.core.OAuth2TokenValidator;
import org.springframework.security.oauth2.core.OAuth2TokenValidatorResult;
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.jwt.JwtDecoder;
import org.springframework.security.oauth2.jwt.JwtException;
import org.springframework.security.oauth2.jwt.JwtTimestampValidator;
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
import org.springframework.security.oauth2.server.resource.authentication.BearerTokenAuthentication;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;
import org.springframework.security.oauth2.server.resource.authentication.JwtIssuerAuthenticationManagerResolver;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;
import org.springframework.security.oauth2.server.resource.introspection.NimbusOpaqueTokenIntrospector;
import org.springframework.security.oauth2.server.resource.introspection.OpaqueTokenIntrospector;
@@ -127,6 +137,8 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.springframework.security.config.Customizer.withDefaults;
import static org.springframework.security.oauth2.core.TestOAuth2AccessTokens.noScopes;
import static org.springframework.security.oauth2.jwt.JwtClaimNames.ISS;
import static org.springframework.security.oauth2.jwt.JwtClaimNames.SUB;
import static org.springframework.security.oauth2.jwt.NimbusJwtDecoder.withJwkSetUri;
import static org.springframework.security.oauth2.jwt.NimbusJwtDecoder.withPublicKey;
import static org.springframework.security.oauth2.jwt.TestJwts.jwt;
@@ -149,7 +161,7 @@ import static org.springframework.web.bind.annotation.RequestMethod.POST;
public class OAuth2ResourceServerConfigurerTests {
private static final String JWT_TOKEN = "token";
private static final String JWT_SUBJECT = "mock-test-subject";
private static final Map<String, Object> JWT_CLAIMS = Collections.singletonMap(JwtClaimNames.SUB, JWT_SUBJECT);
private static final Map<String, Object> JWT_CLAIMS = Collections.singletonMap(SUB, JWT_SUBJECT);
private static final Jwt JWT = jwt().build();
private static final String JWK_SET_URI = "https://mock.org";
private static final JwtAuthenticationToken JWT_AUTHENTICATION_TOKEN =
@@ -1332,6 +1344,50 @@ public class OAuth2ResourceServerConfigurerTests {
verify(http, never()).authenticationProvider(any(AuthenticationProvider.class));
}
// -- authentication manager resolver
@Test
public void getWhenMultipleIssuersThenUsesIssuerClaimToDifferentiate() throws Exception {
this.spring.register(WebServerConfig.class, MultipleIssuersConfig.class, BasicController.class).autowire();
MockWebServer server = this.spring.getContext().getBean(MockWebServer.class);
String metadata = "{\n"
+ " \"issuer\": \"%s\", \n"
+ " \"jwks_uri\": \"%s/.well-known/jwks.json\" \n"
+ "}";
String jwkSet = jwkSet();
String issuerOne = server.url("/issuerOne").toString();
String issuerTwo = server.url("/issuerTwo").toString();
String issuerThree = server.url("/issuerThree").toString();
String jwtOne = jwtFromIssuer(issuerOne);
String jwtTwo = jwtFromIssuer(issuerTwo);
String jwtThree = jwtFromIssuer(issuerThree);
mockWebServer(String.format(metadata, issuerOne, issuerOne));
mockWebServer(jwkSet);
this.mvc.perform(get("/authenticated")
.with(bearerToken(jwtOne)))
.andExpect(status().isOk())
.andExpect(content().string("test-subject"));
mockWebServer(String.format(metadata, issuerTwo, issuerTwo));
mockWebServer(jwkSet);
this.mvc.perform(get("/authenticated")
.with(bearerToken(jwtTwo)))
.andExpect(status().isOk())
.andExpect(content().string("test-subject"));
mockWebServer(String.format(metadata, issuerThree, issuerThree));
mockWebServer(jwkSet);
this.mvc.perform(get("/authenticated")
.with(bearerToken(jwtThree)))
.andExpect(status().isUnauthorized())
.andExpect(invalidTokenHeader("Invalid issuer"));
}
// -- Incorrect Configuration
@Test
@@ -2070,6 +2126,26 @@ public class OAuth2ResourceServerConfigurerTests {
}
}
@EnableWebSecurity
static class MultipleIssuersConfig extends WebSecurityConfigurerAdapter {
@Autowired
MockWebServer web;
@Override
protected void configure(HttpSecurity http) throws Exception {
String issuerOne = this.web.url("/issuerOne").toString();
String issuerTwo = this.web.url("/issuerTwo").toString();
JwtIssuerAuthenticationManagerResolver authenticationManagerResolver =
new JwtIssuerAuthenticationManagerResolver(issuerOne, issuerTwo);
// @formatter:off
http
.oauth2ResourceServer()
.authenticationManagerResolver(authenticationManagerResolver);
// @formatter:on
}
}
@EnableWebSecurity
static class AuthenticationManagerResolverPlusOtherConfig extends WebSecurityConfigurerAdapter {
@Override
@@ -2257,6 +2333,23 @@ public class OAuth2ResourceServerConfigurerTests {
", error_uri=\"https://tools.ietf.org/html/rfc6750#section-3.1\"");
}
private String jwkSet() {
return new JWKSet(new RSAKey.Builder(TestKeys.DEFAULT_PUBLIC_KEY)
.keyID("1").build()).toString();
}
private String jwtFromIssuer(String issuer) throws Exception {
Map<String, Object> claims = new HashMap<>();
claims.put(ISS, issuer);
claims.put(SUB, "test-subject");
claims.put("scope", "message:read");
JWSObject jws = new JWSObject(
new JWSHeader.Builder(JWSAlgorithm.RS256).keyID("1").build(),
new Payload(new JSONObject(claims)));
jws.sign(new RSASSASigner(TestKeys.DEFAULT_PRIVATE_KEY));
return jws.serialize();
}
private void mockWebServer(String response) {
this.web.enqueue(new MockResponse()
.setResponseCode(200)