|
17 | 17 | package org.springframework.boot.autoconfigure.security.oauth2.resource.reactive;
|
18 | 18 |
|
19 | 19 | import java.io.IOException;
|
| 20 | +import java.net.MalformedURLException; |
| 21 | +import java.net.URL; |
| 22 | +import java.time.Instant; |
20 | 23 | import java.util.Collection;
|
21 | 24 | import java.util.Collections;
|
22 | 25 | import java.util.HashMap;
|
@@ -423,20 +426,108 @@ void autoConfigurationShouldConfigureIssuerAndAudienceJwtValidatorIfPropertyProv
|
423 | 426 | String issuer = this.server.url(path).toString();
|
424 | 427 | String cleanIssuerPath = cleanIssuerPath(issuer);
|
425 | 428 | setupMockResponse(cleanIssuerPath);
|
426 |
| - this.contextRunner |
427 |
| - .withPropertyValues("spring.security.oauth2.resourceserver.jwt.jwk-set-uri=https://jwk-set-uri.com", |
428 |
| - "spring.security.oauth2.resourceserver.jwt.issuer-uri=http://" + this.server.getHostName() + ":" |
429 |
| - + this.server.getPort() + "/" + path, |
430 |
| - "spring.security.oauth2.resourceserver.jwt.audience=http://test-audience.com") |
| 429 | + String issuerUri = "http://" + this.server.getHostName() + ":" + this.server.getPort() + "/" + path; |
| 430 | + this.contextRunner.withPropertyValues( |
| 431 | + "spring.security.oauth2.resourceserver.jwt.jwk-set-uri=https://jwk-set-uri.com", |
| 432 | + "spring.security.oauth2.resourceserver.jwt.issuer-uri=" + issuerUri, |
| 433 | + "spring.security.oauth2.resourceserver.jwt.audiences=https://test-audience.com,https://test-audience1.com") |
431 | 434 | .run((context) -> {
|
432 | 435 | assertThat(context).hasSingleBean(ReactiveJwtDecoder.class);
|
433 | 436 | ReactiveJwtDecoder reactiveJwtDecoder = context.getBean(ReactiveJwtDecoder.class);
|
| 437 | + validate(issuerUri, reactiveJwtDecoder); |
| 438 | + }); |
| 439 | + } |
| 440 | + |
| 441 | + @SuppressWarnings("unchecked") |
| 442 | + private void validate(String issuerUri, ReactiveJwtDecoder jwtDecoder) throws MalformedURLException { |
| 443 | + DelegatingOAuth2TokenValidator<Jwt> jwtValidator = (DelegatingOAuth2TokenValidator<Jwt>) ReflectionTestUtils |
| 444 | + .getField(jwtDecoder, "jwtValidator"); |
| 445 | + Jwt.Builder builder = jwt().claim("aud", Collections.singletonList("https://test-audience.com")); |
| 446 | + if (issuerUri != null) { |
| 447 | + builder.claim("iss", new URL(issuerUri)); |
| 448 | + } |
| 449 | + Jwt jwt = builder.build(); |
| 450 | + assertThat(jwtValidator.validate(jwt).hasErrors()).isFalse(); |
| 451 | + Collection<OAuth2TokenValidator<Jwt>> delegates = (Collection<OAuth2TokenValidator<Jwt>>) ReflectionTestUtils |
| 452 | + .getField(jwtValidator, "tokenValidators"); |
| 453 | + validateDelegates(issuerUri, delegates); |
| 454 | + } |
| 455 | + |
| 456 | + @SuppressWarnings("unchecked") |
| 457 | + private void validateDelegates(String issuerUri, Collection<OAuth2TokenValidator<Jwt>> delegates) { |
| 458 | + assertThat(delegates).hasAtLeastOneElementOfType(JwtClaimValidator.class); |
| 459 | + OAuth2TokenValidator<Jwt> delegatingValidator = delegates.stream() |
| 460 | + .filter((v) -> v instanceof DelegatingOAuth2TokenValidator).findFirst().get(); |
| 461 | + Collection<OAuth2TokenValidator<Jwt>> nestedDelegates = (Collection<OAuth2TokenValidator<Jwt>>) ReflectionTestUtils |
| 462 | + .getField(delegatingValidator, "tokenValidators"); |
| 463 | + if (issuerUri != null) { |
| 464 | + assertThat(nestedDelegates).hasAtLeastOneElementOfType(JwtIssuerValidator.class); |
| 465 | + } |
| 466 | + } |
| 467 | + |
| 468 | + @SuppressWarnings("unchecked") |
| 469 | + @Test |
| 470 | + void autoConfigurationShouldConfigureAudienceValidatorIfPropertyProvidedAndIssuerUri() throws Exception { |
| 471 | + this.server = new MockWebServer(); |
| 472 | + this.server.start(); |
| 473 | + String path = "test"; |
| 474 | + String issuer = this.server.url(path).toString(); |
| 475 | + String cleanIssuerPath = cleanIssuerPath(issuer); |
| 476 | + setupMockResponse(cleanIssuerPath); |
| 477 | + String issuerUri = "http://" + this.server.getHostName() + ":" + this.server.getPort() + "/" + path; |
| 478 | + this.contextRunner.withPropertyValues("spring.security.oauth2.resourceserver.jwt.issuer-uri=" + issuerUri, |
| 479 | + "spring.security.oauth2.resourceserver.jwt.audiences=https://test-audience.com,https://test-audience1.com") |
| 480 | + .run((context) -> { |
| 481 | + SupplierReactiveJwtDecoder supplierJwtDecoderBean = context |
| 482 | + .getBean(SupplierReactiveJwtDecoder.class); |
| 483 | + Mono<ReactiveJwtDecoder> jwtDecoderSupplier = (Mono<ReactiveJwtDecoder>) ReflectionTestUtils |
| 484 | + .getField(supplierJwtDecoderBean, "jwtDecoderMono"); |
| 485 | + ReactiveJwtDecoder jwtDecoder = jwtDecoderSupplier.block(); |
| 486 | + validate(issuerUri, jwtDecoder); |
| 487 | + }); |
| 488 | + } |
| 489 | + |
| 490 | + @SuppressWarnings("unchecked") |
| 491 | + @Test |
| 492 | + void autoConfigurationShouldConfigureAudienceValidatorIfPropertyProvidedAndPublicKey() throws Exception { |
| 493 | + this.server = new MockWebServer(); |
| 494 | + this.server.start(); |
| 495 | + String path = "test"; |
| 496 | + String issuer = this.server.url(path).toString(); |
| 497 | + String cleanIssuerPath = cleanIssuerPath(issuer); |
| 498 | + setupMockResponse(cleanIssuerPath); |
| 499 | + this.contextRunner.withPropertyValues( |
| 500 | + "spring.security.oauth2.resourceserver.jwt.public-key-location=classpath:public-key-location", |
| 501 | + "spring.security.oauth2.resourceserver.jwt.audiences=https://test-audience.com,https://test-audience1.com") |
| 502 | + .run((context) -> { |
| 503 | + assertThat(context).hasSingleBean(ReactiveJwtDecoder.class); |
| 504 | + ReactiveJwtDecoder jwtDecoder = context.getBean(ReactiveJwtDecoder.class); |
| 505 | + validate(null, jwtDecoder); |
| 506 | + }); |
| 507 | + } |
| 508 | + |
| 509 | + @SuppressWarnings("unchecked") |
| 510 | + @Test |
| 511 | + void audienceValidatorWhenAudienceInvalid() throws Exception { |
| 512 | + this.server = new MockWebServer(); |
| 513 | + this.server.start(); |
| 514 | + String path = "test"; |
| 515 | + String issuer = this.server.url(path).toString(); |
| 516 | + String cleanIssuerPath = cleanIssuerPath(issuer); |
| 517 | + setupMockResponse(cleanIssuerPath); |
| 518 | + String issuerUri = "http://" + this.server.getHostName() + ":" + this.server.getPort() + "/" + path; |
| 519 | + this.contextRunner.withPropertyValues( |
| 520 | + "spring.security.oauth2.resourceserver.jwt.jwk-set-uri=https://jwk-set-uri.com", |
| 521 | + "spring.security.oauth2.resourceserver.jwt.issuer-uri=" + issuerUri, |
| 522 | + "spring.security.oauth2.resourceserver.jwt.audiences=https://test-audience.com,https://test-audience1.com") |
| 523 | + .run((context) -> { |
| 524 | + assertThat(context).hasSingleBean(ReactiveJwtDecoder.class); |
| 525 | + ReactiveJwtDecoder jwtDecoder = context.getBean(ReactiveJwtDecoder.class); |
434 | 526 | DelegatingOAuth2TokenValidator<Jwt> jwtValidator = (DelegatingOAuth2TokenValidator<Jwt>) ReflectionTestUtils
|
435 |
| - .getField(reactiveJwtDecoder, "jwtValidator"); |
436 |
| - Collection<OAuth2TokenValidator<Jwt>> tokenValidators = (Collection<OAuth2TokenValidator<Jwt>>) ReflectionTestUtils |
437 |
| - .getField(jwtValidator, "tokenValidators"); |
438 |
| - assertThat(tokenValidators).hasAtLeastOneElementOfType(JwtIssuerValidator.class); |
439 |
| - assertThat(tokenValidators).hasAtLeastOneElementOfType(JwtClaimValidator.class); |
| 527 | + .getField(jwtDecoder, "jwtValidator"); |
| 528 | + Jwt jwt = jwt().claim("iss", new URL(issuerUri)) |
| 529 | + .claim("aud", Collections.singletonList("https://other-audience.com")).build(); |
| 530 | + assertThat(jwtValidator.validate(jwt).hasErrors()).isTrue(); |
440 | 531 | });
|
441 | 532 | }
|
442 | 533 |
|
@@ -508,6 +599,19 @@ private Map<String, Object> getResponse(String issuer) {
|
508 | 599 | return response;
|
509 | 600 | }
|
510 | 601 |
|
| 602 | + static Jwt.Builder jwt() { |
| 603 | + // @formatter:off |
| 604 | + return Jwt.withTokenValue("token") |
| 605 | + .header("alg", "none") |
| 606 | + .expiresAt(Instant.MAX) |
| 607 | + .issuedAt(Instant.MIN) |
| 608 | + .issuer("https://issuer.example.org") |
| 609 | + .jti("jti") |
| 610 | + .notBefore(Instant.MIN) |
| 611 | + .subject("mock-test-subject"); |
| 612 | + // @formatter:on |
| 613 | + } |
| 614 | + |
511 | 615 | @EnableWebFluxSecurity
|
512 | 616 | static class TestConfig {
|
513 | 617 |
|
|
0 commit comments