diff --git a/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/JwtAudienceValidator.java b/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/JwtAudienceValidator.java index 8efe933113e..740ce0bdae2 100644 --- a/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/JwtAudienceValidator.java +++ b/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/JwtAudienceValidator.java @@ -33,18 +33,29 @@ public final class JwtAudienceValidator implements OAuth2TokenValidator { private final JwtClaimValidator> validator; /** - * Constructs a {@link JwtAudienceValidator} using the provided parameters + * Constructs a {@link JwtAudienceValidator} using the provided parameters with + * {@link JwtClaimNames#ISS "iss"} claim is REQUIRED * @param audience - The audience that each {@link Jwt} should have. */ public JwtAudienceValidator(String audience) { + this(audience, true); + } + + /** + * Constructs a {@link JwtIssuerValidator} using the provided parameters + * @param audience - The audience that each {@link Jwt} should have. + * @param required -{@code true} if the {@link JwtClaimNames#AUD "aud"} claim is + * REQUIRED in the {@link Jwt}, {@code false} otherwise + */ + public JwtAudienceValidator(String audience, boolean required) { Assert.notNull(audience, "audience cannot be null"); this.validator = new JwtClaimValidator<>(JwtClaimNames.AUD, - (claimValue) -> (claimValue != null) && claimValue.contains(audience)); + (claimValue) -> (claimValue != null) ? claimValue.contains(audience) : !required); } @Override public OAuth2TokenValidatorResult validate(Jwt token) { - Assert.notNull(token, "token cannot be null"); + Assert.notNull(token, "jwt cannot be null"); return this.validator.validate(token); } diff --git a/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/JwtIssuerValidator.java b/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/JwtIssuerValidator.java index ffdd5a142ce..8de40af298e 100644 --- a/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/JwtIssuerValidator.java +++ b/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/JwtIssuerValidator.java @@ -16,8 +16,6 @@ package org.springframework.security.oauth2.jwt; -import java.util.function.Predicate; - import org.springframework.security.oauth2.core.OAuth2TokenValidator; import org.springframework.security.oauth2.core.OAuth2TokenValidatorResult; import org.springframework.util.Assert; @@ -33,19 +31,29 @@ public final class JwtIssuerValidator implements OAuth2TokenValidator { private final JwtClaimValidator validator; /** - * Constructs a {@link JwtIssuerValidator} using the provided parameters + * Constructs a {@link JwtIssuerValidator} using the provided parameters with + * {@link JwtClaimNames#ISS "iss"} claim is REQUIRED * @param issuer - The issuer that each {@link Jwt} should have. */ public JwtIssuerValidator(String issuer) { - Assert.notNull(issuer, "issuer cannot be null"); + this(issuer, true); + } - Predicate testClaimValue = (claimValue) -> (claimValue != null) && issuer.equals(claimValue.toString()); - this.validator = new JwtClaimValidator<>(JwtClaimNames.ISS, testClaimValue); + /** + * Constructs a {@link JwtIssuerValidator} using the provided parameters + * @param issuer - The issuer that each {@link Jwt} should have. + * @param required -{@code true} if the {@link JwtClaimNames#ISS "iss"} claim is + * REQUIRED in the {@link Jwt}, {@code false} otherwise + */ + public JwtIssuerValidator(String issuer, boolean required) { + Assert.notNull(issuer, "issuer cannot be null"); + this.validator = new JwtClaimValidator<>(JwtClaimNames.ISS, + (claimValue) -> (claimValue != null) ? issuer.equals(claimValue.toString()) : !required); } @Override public OAuth2TokenValidatorResult validate(Jwt token) { - Assert.notNull(token, "token cannot be null"); + Assert.notNull(token, "jwt cannot be null"); return this.validator.validate(token); } diff --git a/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/JwtTimestampValidator.java b/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/JwtTimestampValidator.java index d191b8b11a6..80ed063f7f3 100644 --- a/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/JwtTimestampValidator.java +++ b/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/JwtTimestampValidator.java @@ -52,6 +52,8 @@ public final class JwtTimestampValidator implements OAuth2TokenValidator { private static final Duration DEFAULT_MAX_CLOCK_SKEW = Duration.of(60, ChronoUnit.SECONDS); + private final boolean required; + private final Duration clockSkew; private Clock clock = Clock.systemUTC(); @@ -60,11 +62,20 @@ public final class JwtTimestampValidator implements OAuth2TokenValidator { * A basic instance with no custom verification and the default max clock skew */ public JwtTimestampValidator() { - this(DEFAULT_MAX_CLOCK_SKEW); + this(DEFAULT_MAX_CLOCK_SKEW, false); + } + + public JwtTimestampValidator(boolean required) { + this(DEFAULT_MAX_CLOCK_SKEW, required); } public JwtTimestampValidator(Duration clockSkew) { + this(clockSkew, false); + } + + public JwtTimestampValidator(Duration clockSkew, boolean required) { Assert.notNull(clockSkew, "clockSkew cannot be null"); + this.required = required; this.clockSkew = clockSkew; } @@ -72,13 +83,17 @@ public JwtTimestampValidator(Duration clockSkew) { public OAuth2TokenValidatorResult validate(Jwt jwt) { Assert.notNull(jwt, "jwt cannot be null"); Instant expiry = jwt.getExpiresAt(); + Instant notBefore = jwt.getNotBefore(); + if (this.required && !(expiry != null || notBefore != null)) { + OAuth2Error oAuth2Error = createOAuth2Error("exp and nbf are required"); + return OAuth2TokenValidatorResult.failure(oAuth2Error); + } if (expiry != null) { if (Instant.now(this.clock).minus(this.clockSkew).isAfter(expiry)) { OAuth2Error oAuth2Error = createOAuth2Error(String.format("Jwt expired at %s", jwt.getExpiresAt())); return OAuth2TokenValidatorResult.failure(oAuth2Error); } } - Instant notBefore = jwt.getNotBefore(); if (notBefore != null) { if (Instant.now(this.clock).plus(this.clockSkew).isBefore(notBefore)) { OAuth2Error oAuth2Error = createOAuth2Error(String.format("Jwt used before %s", jwt.getNotBefore())); diff --git a/oauth2/oauth2-jose/src/test/java/org/springframework/security/oauth2/jwt/JwtAudienceValidatorTests.java b/oauth2/oauth2-jose/src/test/java/org/springframework/security/oauth2/jwt/JwtAudienceValidatorTests.java index fac44972bb9..c4fdc1e177c 100644 --- a/oauth2/oauth2-jose/src/test/java/org/springframework/security/oauth2/jwt/JwtAudienceValidatorTests.java +++ b/oauth2/oauth2-jose/src/test/java/org/springframework/security/oauth2/jwt/JwtAudienceValidatorTests.java @@ -31,27 +31,73 @@ */ class JwtAudienceValidatorTests { - private final JwtAudienceValidator validator = new JwtAudienceValidator("audience"); + private final JwtAudienceValidator validatorDefault = new JwtAudienceValidator("audience"); + + private final JwtAudienceValidator validatorRequiredTrue = new JwtAudienceValidator("audience", true); + + private final JwtAudienceValidator validatorRequiredFalse = new JwtAudienceValidator("audience", false); + + @Test + void givenRequiredDefaultJwtWithMatchingAudienceThenShouldValidate() { + Jwt jwt = TestJwts.jwt().audience(List.of("audience")).build(); + OAuth2TokenValidatorResult result = this.validatorDefault.validate(jwt); + assertThat(result).isEqualTo(OAuth2TokenValidatorResult.success()); + } + + @Test + void givenRequiredJwtWithMatchingAudienceThenShouldValidate() { + Jwt jwt = TestJwts.jwt().audience(List.of("audience")).build(); + OAuth2TokenValidatorResult result = this.validatorRequiredTrue.validate(jwt); + assertThat(result).isEqualTo(OAuth2TokenValidatorResult.success()); + } @Test - void givenJwtWithMatchingAudienceThenShouldValidate() { + void givenNotRequiredJwtWithMatchingAudienceThenShouldValidate() { Jwt jwt = TestJwts.jwt().audience(List.of("audience")).build(); - OAuth2TokenValidatorResult result = this.validator.validate(jwt); + OAuth2TokenValidatorResult result = this.validatorRequiredFalse.validate(jwt); assertThat(result).isEqualTo(OAuth2TokenValidatorResult.success()); } @Test - void givenJwtWithoutMatchingAudienceThenShouldValidate() { + void givenRequiredDefaultJwtWithoutMatchingAudienceThenShouldValidate() { Jwt jwt = TestJwts.jwt().audience(List.of("other")).build(); - OAuth2TokenValidatorResult result = this.validator.validate(jwt); + OAuth2TokenValidatorResult result = this.validatorDefault.validate(jwt); assertThat(result.hasErrors()).isTrue(); } @Test - void givenJwtWithoutAudienceThenShouldValidate() { + void givenRequiredJwtWithoutMatchingAudienceThenShouldValidate() { + Jwt jwt = TestJwts.jwt().audience(List.of("other")).build(); + OAuth2TokenValidatorResult result = this.validatorRequiredTrue.validate(jwt); + assertThat(result.hasErrors()).isTrue(); + } + + @Test + void givenNotRequiredJwtWithoutMatchingAudienceThenShouldValidate() { + Jwt jwt = TestJwts.jwt().audience(List.of("other")).build(); + OAuth2TokenValidatorResult result = this.validatorRequiredFalse.validate(jwt); + assertThat(result.hasErrors()).isTrue(); + } + + @Test + void givenRequiredDefaultJwtWithoutAudienceThenShouldValidate() { Jwt jwt = TestJwts.jwt().audience(null).build(); - OAuth2TokenValidatorResult result = this.validator.validate(jwt); + OAuth2TokenValidatorResult result = this.validatorDefault.validate(jwt); assertThat(result.hasErrors()).isTrue(); } + @Test + void givenRequiredJwtWithoutAudienceThenShouldValidate() { + Jwt jwt = TestJwts.jwt().audience(null).build(); + OAuth2TokenValidatorResult result = this.validatorRequiredTrue.validate(jwt); + assertThat(result.hasErrors()).isTrue(); + } + + @Test + void givenNotRequiredJwtWithoutAudienceThenShouldValidate() { + Jwt jwt = TestJwts.jwt().audience(null).build(); + OAuth2TokenValidatorResult result = this.validatorRequiredFalse.validate(jwt); + assertThat(result.hasErrors()).isFalse(); + } + } diff --git a/oauth2/oauth2-jose/src/test/java/org/springframework/security/oauth2/jwt/JwtIssuerValidatorTests.java b/oauth2/oauth2-jose/src/test/java/org/springframework/security/oauth2/jwt/JwtIssuerValidatorTests.java index aa3192f067a..1bc4752a1e0 100644 --- a/oauth2/oauth2-jose/src/test/java/org/springframework/security/oauth2/jwt/JwtIssuerValidatorTests.java +++ b/oauth2/oauth2-jose/src/test/java/org/springframework/security/oauth2/jwt/JwtIssuerValidatorTests.java @@ -34,50 +34,132 @@ public class JwtIssuerValidatorTests { private static final String ISSUER = "https://issuer"; - private final JwtIssuerValidator validator = new JwtIssuerValidator(ISSUER); + private final JwtIssuerValidator validatorDefault = new JwtIssuerValidator(ISSUER); + + private final JwtIssuerValidator validatorRequiredTrue = new JwtIssuerValidator(ISSUER, true); + + private final JwtIssuerValidator validatorRequiredFalse = new JwtIssuerValidator(ISSUER, false); + + @Test + public void validateWhenRequiredDefaultAndIssuerMatchesThenReturnsSuccess() { + Jwt jwt = TestJwts.jwt().claim("iss", ISSUER).build(); + // @formatter:off + assertThat(this.validatorDefault.validate(jwt)) + .isEqualTo(OAuth2TokenValidatorResult.success()); + // @formatter:on + } + + @Test + public void validateWhenRequiredAndIssuerMatchesThenReturnsSuccess() { + Jwt jwt = TestJwts.jwt().claim("iss", ISSUER).build(); + // @formatter:off + assertThat(this.validatorRequiredTrue.validate(jwt)) + .isEqualTo(OAuth2TokenValidatorResult.success()); + // @formatter:on + } @Test - public void validateWhenIssuerMatchesThenReturnsSuccess() { + public void validateWhenNotRequiredAndIssuerMatchesThenReturnsSuccess() { Jwt jwt = TestJwts.jwt().claim("iss", ISSUER).build(); // @formatter:off - assertThat(this.validator.validate(jwt)) + assertThat(this.validatorRequiredFalse.validate(jwt)) .isEqualTo(OAuth2TokenValidatorResult.success()); // @formatter:on } @Test - public void validateWhenIssuerUrlMatchesThenReturnsSuccess() throws MalformedURLException { + public void validateWhenRequiredDefaultAndIssuerUrlMatchesThenReturnsSuccess() throws MalformedURLException { + Jwt jwt = TestJwts.jwt().claim("iss", new URL(ISSUER)).build(); + + assertThat(this.validatorDefault.validate(jwt)).isEqualTo(OAuth2TokenValidatorResult.success()); + } + + @Test + public void validateWhenRequiredAndIssuerUrlMatchesThenReturnsSuccess() throws MalformedURLException { + Jwt jwt = TestJwts.jwt().claim("iss", new URL(ISSUER)).build(); + + assertThat(this.validatorRequiredTrue.validate(jwt)).isEqualTo(OAuth2TokenValidatorResult.success()); + } + + @Test + public void validateWhenNotRequiredAndIssuerUrlMatchesThenReturnsSuccess() throws MalformedURLException { Jwt jwt = TestJwts.jwt().claim("iss", new URL(ISSUER)).build(); - assertThat(this.validator.validate(jwt)).isEqualTo(OAuth2TokenValidatorResult.success()); + assertThat(this.validatorRequiredFalse.validate(jwt)).isEqualTo(OAuth2TokenValidatorResult.success()); } @Test - public void validateWhenIssuerMismatchesThenReturnsError() { + public void validateWhenRequiredDefaultAndIssuerMismatchesThenReturnsError() { Jwt jwt = TestJwts.jwt().claim(JwtClaimNames.ISS, "https://other").build(); - OAuth2TokenValidatorResult result = this.validator.validate(jwt); + OAuth2TokenValidatorResult result = this.validatorDefault.validate(jwt); assertThat(result.getErrors()).isNotEmpty(); } @Test - public void validateWhenIssuerUrlMismatchesThenReturnsError() throws MalformedURLException { + public void validateWhenRequiredAndIssuerMismatchesThenReturnsError() { + Jwt jwt = TestJwts.jwt().claim(JwtClaimNames.ISS, "https://other").build(); + OAuth2TokenValidatorResult result = this.validatorRequiredTrue.validate(jwt); + assertThat(result.getErrors()).isNotEmpty(); + } + + @Test + public void validateWhenNotRequiredAndIssuerMismatchesThenReturnsError() { + Jwt jwt = TestJwts.jwt().claim(JwtClaimNames.ISS, "https://other").build(); + OAuth2TokenValidatorResult result = this.validatorRequiredFalse.validate(jwt); + assertThat(result.getErrors()).isNotEmpty(); + } + + @Test + public void validateWhenRequiredDefaultAndIssuerUrlMismatchesThenReturnsError() throws MalformedURLException { Jwt jwt = TestJwts.jwt().claim(JwtClaimNames.ISS, new URL("https://other")).build(); - OAuth2TokenValidatorResult result = this.validator.validate(jwt); + OAuth2TokenValidatorResult result = this.validatorDefault.validate(jwt); + + assertThat(result.getErrors()).isNotEmpty(); + } + + @Test + public void validateWhenRequiredAndIssuerUrlMismatchesThenReturnsError() throws MalformedURLException { + Jwt jwt = TestJwts.jwt().claim(JwtClaimNames.ISS, new URL("https://other")).build(); + + OAuth2TokenValidatorResult result = this.validatorRequiredTrue.validate(jwt); + + assertThat(result.getErrors()).isNotEmpty(); + } + + @Test + public void validateWhenNotRequiredAndIssuerUrlMismatchesThenReturnsError() throws MalformedURLException { + Jwt jwt = TestJwts.jwt().claim(JwtClaimNames.ISS, new URL("https://other")).build(); + + OAuth2TokenValidatorResult result = this.validatorRequiredFalse.validate(jwt); + + assertThat(result.getErrors()).isNotEmpty(); + } + @Test + public void validateWhenRequiredDefaultAndJwtHasNoIssuerThenReturnsError() { + Jwt jwt = TestJwts.jwt().claim(JwtClaimNames.AUD, "https://aud").build(); + OAuth2TokenValidatorResult result = this.validatorDefault.validate(jwt); assertThat(result.getErrors()).isNotEmpty(); } @Test - public void validateWhenJwtHasNoIssuerThenReturnsError() { + public void validateWhenRequiredAndJwtHasNoIssuerThenReturnsError() { Jwt jwt = TestJwts.jwt().claim(JwtClaimNames.AUD, "https://aud").build(); - OAuth2TokenValidatorResult result = this.validator.validate(jwt); + OAuth2TokenValidatorResult result = this.validatorRequiredTrue.validate(jwt); + assertThat(result.getErrors()).isNotEmpty(); + } + + @Test + public void validateWhenNotRequiredAndJwtHasNoIssuerThenReturnsError() { + Jwt jwt = TestJwts.jwt().claim(JwtClaimNames.AUD, "https://aud").build(); + OAuth2TokenValidatorResult result = this.validatorRequiredFalse.validate(jwt); assertThat(result.getErrors()).isNotEmpty(); } // gh-6073 @Test - public void validateWhenIssuerMatchesAndIsNotAUriThenReturnsSuccess() { + public void validateWhenRequiredDefaultAndIssuerMatchesAndIsNotAUriThenReturnsSuccess() { Jwt jwt = TestJwts.jwt().claim(JwtClaimNames.ISS, "issuer").build(); JwtIssuerValidator validator = new JwtIssuerValidator("issuer"); // @formatter:off @@ -86,20 +168,74 @@ public void validateWhenIssuerMatchesAndIsNotAUriThenReturnsSuccess() { // @formatter:on } + // gh-6073 + @Test + public void validateWhenRequiredAndIssuerMatchesAndIsNotAUriThenReturnsSuccess() { + Jwt jwt = TestJwts.jwt().claim(JwtClaimNames.ISS, "issuer").build(); + JwtIssuerValidator validator = new JwtIssuerValidator("issuer", true); + // @formatter:off + assertThat(validator.validate(jwt)) + .isEqualTo(OAuth2TokenValidatorResult.success()); + // @formatter:on + } + + // gh-6073 @Test - public void validateWhenJwtIsNullThenThrowsIllegalArgumentException() { + public void validateWhenNotRequiredAndIssuerMatchesAndIsNotAUriThenReturnsSuccess() { + Jwt jwt = TestJwts.jwt().claim(JwtClaimNames.ISS, "issuer").build(); + JwtIssuerValidator validator = new JwtIssuerValidator("issuer", false); + // @formatter:off + assertThat(validator.validate(jwt)) + .isEqualTo(OAuth2TokenValidatorResult.success()); + // @formatter:on + } + + @Test + public void validateWhenRequiredDefaultAndJwtIsNullThenThrowsIllegalArgumentException() { + // @formatter:off + assertThatIllegalArgumentException() + .isThrownBy(() -> this.validatorDefault.validate(null)); + // @formatter:on + } + + @Test + public void validateWhenRequiredAndJwtIsNullThenThrowsIllegalArgumentException() { + // @formatter:off + assertThatIllegalArgumentException() + .isThrownBy(() -> this.validatorRequiredTrue.validate(null)); + // @formatter:on + } + + @Test + public void validateWhenNotRequiredAndJwtIsNullThenThrowsIllegalArgumentException() { // @formatter:off assertThatIllegalArgumentException() - .isThrownBy(() -> this.validator.validate(null)); + .isThrownBy(() -> this.validatorRequiredFalse.validate(null)); // @formatter:on } @Test - public void constructorWhenNullIssuerIsGivenThenThrowsIllegalArgumentException() { + public void constructorWhenRequiredDefaultAndNullIssuerIsGivenThenThrowsIllegalArgumentException() { // @formatter:off assertThatIllegalArgumentException() .isThrownBy(() -> new JwtIssuerValidator(null)); // @formatter:on } + @Test + public void constructorWhenRequiredAndNullIssuerIsGivenThenThrowsIllegalArgumentException() { + // @formatter:off + assertThatIllegalArgumentException() + .isThrownBy(() -> new JwtIssuerValidator(null, true)); + // @formatter:on + } + + @Test + public void constructorWhenNotRequiredAndNullIssuerIsGivenThenThrowsIllegalArgumentException() { + // @formatter:off + assertThatIllegalArgumentException() + .isThrownBy(() -> new JwtIssuerValidator(null, false)); + // @formatter:on + } + } diff --git a/oauth2/oauth2-jose/src/test/java/org/springframework/security/oauth2/jwt/JwtTimestampValidatorTests.java b/oauth2/oauth2-jose/src/test/java/org/springframework/security/oauth2/jwt/JwtTimestampValidatorTests.java index 72164cf21b7..8cdb547b0d2 100644 --- a/oauth2/oauth2-jose/src/test/java/org/springframework/security/oauth2/jwt/JwtTimestampValidatorTests.java +++ b/oauth2/oauth2-jose/src/test/java/org/springframework/security/oauth2/jwt/JwtTimestampValidatorTests.java @@ -128,34 +128,115 @@ public void validateWhenConfiguredWithFixedClockThenValidatesUsingFixedTime() { } @Test - public void validateWhenNeitherExpiryNorNotBeforeIsSpecifiedThenReturnsSuccessfulResult() { - Jwt jwt = TestJwts.jwt().claims((c) -> c.remove(JwtClaimNames.EXP)).build(); + public void validateWhenDefaultRequiredAndNeitherExpiryNorNotBeforeIsSpecifiedThenReturnsSuccessfulResult() { + Jwt jwt = TestJwts.jwt().claims((c) -> { + c.remove(JwtClaimNames.EXP); + c.remove(JwtClaimNames.NBF); + }).build(); JwtTimestampValidator jwtValidator = new JwtTimestampValidator(); assertThat(jwtValidator.validate(jwt).hasErrors()).isFalse(); } @Test - public void validateWhenNotBeforeIsValidAndExpiryIsNotSpecifiedThenReturnsSuccessfulResult() { + public void validateWhenNotRequiredAndNeitherExpiryNorNotBeforeIsSpecifiedThenReturnsSuccessfulResult() { + Jwt jwt = TestJwts.jwt().claims((c) -> { + c.remove(JwtClaimNames.EXP); + c.remove(JwtClaimNames.NBF); + }).build(); + JwtTimestampValidator jwtValidator = new JwtTimestampValidator(false); + assertThat(jwtValidator.validate(jwt).hasErrors()).isFalse(); + } + + @Test + public void validateWhenRequiredAndNoExpiryIsSpecifiedThenReturnsSuccessfulResult() { + Jwt jwt = TestJwts.jwt().claims((c) -> c.remove(JwtClaimNames.EXP)).build(); + JwtTimestampValidator jwtValidator = new JwtTimestampValidator(true); + assertThat(jwtValidator.validate(jwt).hasErrors()).isFalse(); + } + + @Test + public void validateWhenRequiredAndNoNotBeforeIsSpecifiedThenReturnsSuccessfulResult() { + Jwt jwt = TestJwts.jwt().claims((c) -> c.remove(JwtClaimNames.NBF)).build(); + JwtTimestampValidator jwtValidator = new JwtTimestampValidator(true); + assertThat(jwtValidator.validate(jwt).hasErrors()).isFalse(); + } + + @Test + public void validateWhenRequiredAndNeitherExpiryNorNotBeforeIsSpecifiedThenReturnsSuccessfulResult() { + Jwt jwt = TestJwts.jwt().claims((c) -> { + c.remove(JwtClaimNames.EXP); + c.remove(JwtClaimNames.NBF); + }).build(); + JwtTimestampValidator jwtValidator = new JwtTimestampValidator(true); + assertThat(jwtValidator.validate(jwt).hasErrors()).isTrue(); + } + + @Test + public void validateWhenDefaultAndRequiredNotBeforeIsValidAndExpiryIsNotSpecifiedThenReturnsSuccessfulResult() { Jwt jwt = TestJwts.jwt().claims((c) -> c.remove(JwtClaimNames.EXP)).notBefore(Instant.MIN).build(); JwtTimestampValidator jwtValidator = new JwtTimestampValidator(); assertThat(jwtValidator.validate(jwt).hasErrors()).isFalse(); } @Test - public void validateWhenExpiryIsValidAndNotBeforeIsNotSpecifiedThenReturnsSuccessfulResult() { + public void validateWhenNotRequiredAndNotBeforeIsValidAndExpiryIsNotSpecifiedThenReturnsSuccessfulResult() { + Jwt jwt = TestJwts.jwt().claims((c) -> c.remove(JwtClaimNames.EXP)).notBefore(Instant.MIN).build(); + JwtTimestampValidator jwtValidator = new JwtTimestampValidator(false); + assertThat(jwtValidator.validate(jwt).hasErrors()).isFalse(); + } + + @Test + public void validateWhenRequiredAndNotBeforeIsValidAndExpiryIsNotSpecifiedThenReturnsSuccessfulResult() { + Jwt jwt = TestJwts.jwt().claims((c) -> c.remove(JwtClaimNames.EXP)).notBefore(Instant.MIN).build(); + JwtTimestampValidator jwtValidator = new JwtTimestampValidator(true); + assertThat(jwtValidator.validate(jwt).hasErrors()).isFalse(); + } + + @Test + public void validateWhenDefaultAndRequiredExpiryIsValidAndNotBeforeIsNotSpecifiedThenReturnsSuccessfulResult() { Jwt jwt = TestJwts.jwt().build(); JwtTimestampValidator jwtValidator = new JwtTimestampValidator(); assertThat(jwtValidator.validate(jwt).hasErrors()).isFalse(); } @Test - public void validateWhenBothExpiryAndNotBeforeAreValidThenReturnsSuccessfulResult() { + public void validateWhenNotRequiredAndExpiryIsValidAndNotBeforeIsNotSpecifiedThenReturnsSuccessfulResult() { + Jwt jwt = TestJwts.jwt().build(); + JwtTimestampValidator jwtValidator = new JwtTimestampValidator(false); + assertThat(jwtValidator.validate(jwt).hasErrors()).isFalse(); + } + + @Test + public void validateWhenRequiredAndExpiryIsValidAndNotBeforeIsNotSpecifiedThenReturnsSuccessfulResult() { + Jwt jwt = TestJwts.jwt().build(); + JwtTimestampValidator jwtValidator = new JwtTimestampValidator(true); + assertThat(jwtValidator.validate(jwt).hasErrors()).isFalse(); + } + + @Test + public void validateWhenDefaultRequiredAndBothExpiryAndNotBeforeAreValidThenReturnsSuccessfulResult() { Jwt jwt = TestJwts.jwt().expiresAt(Instant.now(MOCK_NOW)).notBefore(Instant.now(MOCK_NOW)).build(); JwtTimestampValidator jwtValidator = new JwtTimestampValidator(Duration.ofNanos(0)); jwtValidator.setClock(MOCK_NOW); assertThat(jwtValidator.validate(jwt).hasErrors()).isFalse(); } + @Test + public void validateWhenNotRequiredAndBothExpiryAndNotBeforeAreValidThenReturnsSuccessfulResult() { + Jwt jwt = TestJwts.jwt().expiresAt(Instant.now(MOCK_NOW)).notBefore(Instant.now(MOCK_NOW)).build(); + JwtTimestampValidator jwtValidator = new JwtTimestampValidator(Duration.ofNanos(0), false); + jwtValidator.setClock(MOCK_NOW); + assertThat(jwtValidator.validate(jwt).hasErrors()).isFalse(); + } + + @Test + public void validateWhenRequiredAndBothExpiryAndNotBeforeAreValidThenReturnsSuccessfulResult() { + Jwt jwt = TestJwts.jwt().expiresAt(Instant.now(MOCK_NOW)).notBefore(Instant.now(MOCK_NOW)).build(); + JwtTimestampValidator jwtValidator = new JwtTimestampValidator(Duration.ofNanos(0), true); + jwtValidator.setClock(MOCK_NOW); + assertThat(jwtValidator.validate(jwt).hasErrors()).isFalse(); + } + @Test public void setClockWhenInvokedWithNullThenThrowsIllegalArgumentException() { JwtTimestampValidator jwtValidator = new JwtTimestampValidator();