|
28 | 28 | import java.time.ZoneId;
|
29 | 29 | import java.util.Base64;
|
30 | 30 | import java.util.Collections;
|
| 31 | +import java.util.HashMap; |
31 | 32 | import java.util.Map;
|
32 | 33 | import java.util.stream.Collectors;
|
33 | 34 | import javax.annotation.PreDestroy;
|
34 | 35 |
|
| 36 | +import com.nimbusds.jose.JWSAlgorithm; |
| 37 | +import com.nimbusds.jose.JWSHeader; |
| 38 | +import com.nimbusds.jose.JWSObject; |
| 39 | +import com.nimbusds.jose.Payload; |
| 40 | +import com.nimbusds.jose.crypto.RSASSASigner; |
| 41 | +import com.nimbusds.jose.jwk.JWKSet; |
| 42 | +import com.nimbusds.jose.jwk.RSAKey; |
| 43 | +import net.minidev.json.JSONObject; |
35 | 44 | import okhttp3.mockwebserver.MockResponse;
|
36 | 45 | import okhttp3.mockwebserver.MockWebServer;
|
37 | 46 | import org.hamcrest.core.AllOf;
|
|
82 | 91 | import org.springframework.security.oauth2.core.OAuth2Error;
|
83 | 92 | import org.springframework.security.oauth2.core.OAuth2TokenValidator;
|
84 | 93 | import org.springframework.security.oauth2.core.OAuth2TokenValidatorResult;
|
| 94 | +import org.springframework.security.oauth2.jose.TestKeys; |
85 | 95 | import org.springframework.security.oauth2.jwt.Jwt;
|
86 |
| -import org.springframework.security.oauth2.jwt.JwtClaimNames; |
87 | 96 | import org.springframework.security.oauth2.jwt.JwtDecoder;
|
88 | 97 | import org.springframework.security.oauth2.jwt.JwtException;
|
89 | 98 | import org.springframework.security.oauth2.jwt.JwtTimestampValidator;
|
90 | 99 | import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
|
91 | 100 | import org.springframework.security.oauth2.server.resource.authentication.BearerTokenAuthentication;
|
92 | 101 | import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;
|
| 102 | +import org.springframework.security.oauth2.server.resource.authentication.JwtIssuerAuthenticationManagerResolver; |
93 | 103 | import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;
|
94 | 104 | import org.springframework.security.oauth2.server.resource.introspection.NimbusOpaqueTokenIntrospector;
|
95 | 105 | import org.springframework.security.oauth2.server.resource.introspection.OpaqueTokenIntrospector;
|
|
127 | 137 | import static org.mockito.Mockito.when;
|
128 | 138 | import static org.springframework.security.config.Customizer.withDefaults;
|
129 | 139 | import static org.springframework.security.oauth2.core.TestOAuth2AccessTokens.noScopes;
|
| 140 | +import static org.springframework.security.oauth2.jwt.JwtClaimNames.ISS; |
| 141 | +import static org.springframework.security.oauth2.jwt.JwtClaimNames.SUB; |
130 | 142 | import static org.springframework.security.oauth2.jwt.NimbusJwtDecoder.withJwkSetUri;
|
131 | 143 | import static org.springframework.security.oauth2.jwt.NimbusJwtDecoder.withPublicKey;
|
132 | 144 | import static org.springframework.security.oauth2.jwt.TestJwts.jwt;
|
|
149 | 161 | public class OAuth2ResourceServerConfigurerTests {
|
150 | 162 | private static final String JWT_TOKEN = "token";
|
151 | 163 | private static final String JWT_SUBJECT = "mock-test-subject";
|
152 |
| - private static final Map<String, Object> JWT_CLAIMS = Collections.singletonMap(JwtClaimNames.SUB, JWT_SUBJECT); |
| 164 | + private static final Map<String, Object> JWT_CLAIMS = Collections.singletonMap(SUB, JWT_SUBJECT); |
153 | 165 | private static final Jwt JWT = jwt().build();
|
154 | 166 | private static final String JWK_SET_URI = "https://mock.org";
|
155 | 167 | private static final JwtAuthenticationToken JWT_AUTHENTICATION_TOKEN =
|
@@ -1332,6 +1344,50 @@ public void getAuthenticationManagerWhenConfiguredAuthenticationManagerThenTakes
|
1332 | 1344 | verify(http, never()).authenticationProvider(any(AuthenticationProvider.class));
|
1333 | 1345 | }
|
1334 | 1346 |
|
| 1347 | + // -- authentication manager resolver |
| 1348 | + |
| 1349 | + @Test |
| 1350 | + public void getWhenMultipleIssuersThenUsesIssuerClaimToDifferentiate() throws Exception { |
| 1351 | + this.spring.register(WebServerConfig.class, MultipleIssuersConfig.class, BasicController.class).autowire(); |
| 1352 | + |
| 1353 | + MockWebServer server = this.spring.getContext().getBean(MockWebServer.class); |
| 1354 | + String metadata = "{\n" |
| 1355 | + + " \"issuer\": \"%s\", \n" |
| 1356 | + + " \"jwks_uri\": \"%s/.well-known/jwks.json\" \n" |
| 1357 | + + "}"; |
| 1358 | + String jwkSet = jwkSet(); |
| 1359 | + String issuerOne = server.url("/issuerOne").toString(); |
| 1360 | + String issuerTwo = server.url("/issuerTwo").toString(); |
| 1361 | + String issuerThree = server.url("/issuerThree").toString(); |
| 1362 | + String jwtOne = jwtFromIssuer(issuerOne); |
| 1363 | + String jwtTwo = jwtFromIssuer(issuerTwo); |
| 1364 | + String jwtThree = jwtFromIssuer(issuerThree); |
| 1365 | + |
| 1366 | + mockWebServer(String.format(metadata, issuerOne, issuerOne)); |
| 1367 | + mockWebServer(jwkSet); |
| 1368 | + |
| 1369 | + this.mvc.perform(get("/authenticated") |
| 1370 | + .with(bearerToken(jwtOne))) |
| 1371 | + .andExpect(status().isOk()) |
| 1372 | + .andExpect(content().string("test-subject")); |
| 1373 | + |
| 1374 | + mockWebServer(String.format(metadata, issuerTwo, issuerTwo)); |
| 1375 | + mockWebServer(jwkSet); |
| 1376 | + |
| 1377 | + this.mvc.perform(get("/authenticated") |
| 1378 | + .with(bearerToken(jwtTwo))) |
| 1379 | + .andExpect(status().isOk()) |
| 1380 | + .andExpect(content().string("test-subject")); |
| 1381 | + |
| 1382 | + mockWebServer(String.format(metadata, issuerThree, issuerThree)); |
| 1383 | + mockWebServer(jwkSet); |
| 1384 | + |
| 1385 | + this.mvc.perform(get("/authenticated") |
| 1386 | + .with(bearerToken(jwtThree))) |
| 1387 | + .andExpect(status().isUnauthorized()) |
| 1388 | + .andExpect(invalidTokenHeader("Invalid issuer")); |
| 1389 | + } |
| 1390 | + |
1335 | 1391 | // -- Incorrect Configuration
|
1336 | 1392 |
|
1337 | 1393 | @Test
|
@@ -2070,6 +2126,26 @@ protected void configure(HttpSecurity http) throws Exception {
|
2070 | 2126 | }
|
2071 | 2127 | }
|
2072 | 2128 |
|
| 2129 | + @EnableWebSecurity |
| 2130 | + static class MultipleIssuersConfig extends WebSecurityConfigurerAdapter { |
| 2131 | + @Autowired |
| 2132 | + MockWebServer web; |
| 2133 | + |
| 2134 | + @Override |
| 2135 | + protected void configure(HttpSecurity http) throws Exception { |
| 2136 | + String issuerOne = this.web.url("/issuerOne").toString(); |
| 2137 | + String issuerTwo = this.web.url("/issuerTwo").toString(); |
| 2138 | + JwtIssuerAuthenticationManagerResolver authenticationManagerResolver = |
| 2139 | + new JwtIssuerAuthenticationManagerResolver(issuerOne, issuerTwo); |
| 2140 | + |
| 2141 | + // @formatter:off |
| 2142 | + http |
| 2143 | + .oauth2ResourceServer() |
| 2144 | + .authenticationManagerResolver(authenticationManagerResolver); |
| 2145 | + // @formatter:on |
| 2146 | + } |
| 2147 | + } |
| 2148 | + |
2073 | 2149 | @EnableWebSecurity
|
2074 | 2150 | static class AuthenticationManagerResolverPlusOtherConfig extends WebSecurityConfigurerAdapter {
|
2075 | 2151 | @Override
|
@@ -2257,6 +2333,23 @@ private static ResultMatcher insufficientScopeHeader() {
|
2257 | 2333 | ", error_uri=\"https://tools.ietf.org/html/rfc6750#section-3.1\"");
|
2258 | 2334 | }
|
2259 | 2335 |
|
| 2336 | + private String jwkSet() { |
| 2337 | + return new JWKSet(new RSAKey.Builder(TestKeys.DEFAULT_PUBLIC_KEY) |
| 2338 | + .keyID("1").build()).toString(); |
| 2339 | + } |
| 2340 | + |
| 2341 | + private String jwtFromIssuer(String issuer) throws Exception { |
| 2342 | + Map<String, Object> claims = new HashMap<>(); |
| 2343 | + claims.put(ISS, issuer); |
| 2344 | + claims.put(SUB, "test-subject"); |
| 2345 | + claims.put("scope", "message:read"); |
| 2346 | + JWSObject jws = new JWSObject( |
| 2347 | + new JWSHeader.Builder(JWSAlgorithm.RS256).keyID("1").build(), |
| 2348 | + new Payload(new JSONObject(claims))); |
| 2349 | + jws.sign(new RSASSASigner(TestKeys.DEFAULT_PRIVATE_KEY)); |
| 2350 | + return jws.serialize(); |
| 2351 | + } |
| 2352 | + |
2260 | 2353 | private void mockWebServer(String response) {
|
2261 | 2354 | this.web.enqueue(new MockResponse()
|
2262 | 2355 | .setResponseCode(200)
|
|
0 commit comments