Browse Source
This introduces OAuth2TokenValidator which allows the customization of validation steps that need to be performing when decoding a string token to a Jwt. At this point, two validators, JwtTimestampValidator and JwtIssuerValidator, are available for use. Fixes: gh-5133pull/5645/head
16 changed files with 1225 additions and 25 deletions
@ -0,0 +1 @@ |
|||||||
|
eyJhbGciOiJSUzI1NiJ9.eyJleHAiOjQ2ODcxNzc5OTB9.RRQvqIZzLweq0iwWUZk1Dpiz6iUmT4bAVhGWqvWNWK3UwJ6aBIYsCRhdVeKQp-g1TxXovMALeAu_2oPmV0wOEEanesAKxjKYcJZQIe8HnVqgug6Ibs04uQ1mJ4RgfntPM-ebsJs-2tjFFkLEYJSkpq2o6SEFW9jBJyW8b8C5UJJahqynonA-Dw5GH1nin5bhhliLuFOmu0Ityt0uJ1Y_vuGsSA-ltVcY52jE4x6GH9NQxLX4ceO1bHSOmdspBoGsE_yo9-zsQw0g1_Iy7uqEjos3xrrboH6Z_u7pRL7AQJ7GNzZlinjYYPANQbYknieZD6beddTK7lvr4DYiPBmXzA |
||||||
@ -0,0 +1,56 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2002-2018 the original author or authors. |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
|
||||||
|
package org.springframework.security.oauth2.core; |
||||||
|
|
||||||
|
import java.util.ArrayList; |
||||||
|
import java.util.Collection; |
||||||
|
|
||||||
|
import org.springframework.util.Assert; |
||||||
|
|
||||||
|
/** |
||||||
|
* A composite validator |
||||||
|
* |
||||||
|
* @param <T> the type of {@link AbstractOAuth2Token} this validator validates |
||||||
|
* |
||||||
|
* @author Josh Cummings |
||||||
|
* @since 5.1 |
||||||
|
*/ |
||||||
|
public final class DelegatingOAuth2TokenValidator<T extends AbstractOAuth2Token> |
||||||
|
implements OAuth2TokenValidator<T> { |
||||||
|
|
||||||
|
private final Collection<OAuth2TokenValidator<T>> tokenValidators; |
||||||
|
|
||||||
|
public DelegatingOAuth2TokenValidator(Collection<OAuth2TokenValidator<T>> tokenValidators) { |
||||||
|
Assert.notNull(tokenValidators, "tokenValidators cannot be null"); |
||||||
|
|
||||||
|
this.tokenValidators = new ArrayList<>(tokenValidators); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* {@inheritDoc} |
||||||
|
*/ |
||||||
|
@Override |
||||||
|
public OAuth2TokenValidatorResult validate(T token) { |
||||||
|
Collection<OAuth2Error> errors = new ArrayList<>(); |
||||||
|
|
||||||
|
for ( OAuth2TokenValidator<T> validator : this.tokenValidators) { |
||||||
|
errors.addAll(validator.validate(token).getErrors()); |
||||||
|
} |
||||||
|
|
||||||
|
return OAuth2TokenValidatorResult.failure(errors); |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,35 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2002-2018 the original author or authors. |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
package org.springframework.security.oauth2.core; |
||||||
|
|
||||||
|
/** |
||||||
|
* Implementations of this interface are responsible for "verifying" |
||||||
|
* the validity and/or constraints of the attributes contained in an OAuth 2.0 Token. |
||||||
|
* |
||||||
|
* @author Joe Grandja |
||||||
|
* @author Josh Cummings |
||||||
|
* @since 5.1 |
||||||
|
*/ |
||||||
|
public interface OAuth2TokenValidator<T extends AbstractOAuth2Token> { |
||||||
|
|
||||||
|
/** |
||||||
|
* Verify the validity and/or constraints of the provided OAuth 2.0 Token. |
||||||
|
* |
||||||
|
* @param token an OAuth 2.0 token |
||||||
|
* @return OAuth2TokenValidationResult the success or failure detail of the validation |
||||||
|
*/ |
||||||
|
OAuth2TokenValidatorResult validate(T token); |
||||||
|
} |
||||||
@ -0,0 +1,92 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2002-2018 the original author or authors. |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
|
||||||
|
package org.springframework.security.oauth2.core; |
||||||
|
|
||||||
|
import java.util.ArrayList; |
||||||
|
import java.util.Arrays; |
||||||
|
import java.util.Collection; |
||||||
|
import java.util.Collections; |
||||||
|
|
||||||
|
import org.springframework.util.Assert; |
||||||
|
|
||||||
|
/** |
||||||
|
* A result emitted from an {@link OAuth2TokenValidator} validation attempt |
||||||
|
* |
||||||
|
* @author Josh Cummings |
||||||
|
* @since 5.1 |
||||||
|
*/ |
||||||
|
public final class OAuth2TokenValidatorResult { |
||||||
|
static final OAuth2TokenValidatorResult NO_ERRORS = new OAuth2TokenValidatorResult(Collections.emptyList()); |
||||||
|
|
||||||
|
private final Collection<OAuth2Error> errors; |
||||||
|
|
||||||
|
private OAuth2TokenValidatorResult(Collection<OAuth2Error> errors) { |
||||||
|
Assert.notNull(errors, "errors cannot be null"); |
||||||
|
this.errors = new ArrayList<>(errors); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Say whether this result indicates success |
||||||
|
* |
||||||
|
* @return whether this result has errors |
||||||
|
*/ |
||||||
|
public boolean hasErrors() { |
||||||
|
return !this.errors.isEmpty(); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Return error details regarding the validation attempt |
||||||
|
* |
||||||
|
* @return the collection of results in this result, if any; returns an empty list otherwise |
||||||
|
*/ |
||||||
|
public Collection<OAuth2Error> getErrors() { |
||||||
|
return this.errors; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Construct a successful {@link OAuth2TokenValidatorResult} |
||||||
|
* |
||||||
|
* @return an {@link OAuth2TokenValidatorResult} with no errors |
||||||
|
*/ |
||||||
|
public static OAuth2TokenValidatorResult success() { |
||||||
|
return NO_ERRORS; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Construct a failure {@link OAuth2TokenValidatorResult} with the provided detail |
||||||
|
* |
||||||
|
* @param errors the list of errors |
||||||
|
* @return an {@link OAuth2TokenValidatorResult} with the errors specified |
||||||
|
*/ |
||||||
|
public static OAuth2TokenValidatorResult failure(OAuth2Error... errors) { |
||||||
|
return failure(Arrays.asList(errors)); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Construct a failure {@link OAuth2TokenValidatorResult} with the provided detail |
||||||
|
* |
||||||
|
* @param errors the list of errors |
||||||
|
* @return an {@link OAuth2TokenValidatorResult} with the errors specified |
||||||
|
*/ |
||||||
|
public static OAuth2TokenValidatorResult failure(Collection<OAuth2Error> errors) { |
||||||
|
if (errors.isEmpty()) { |
||||||
|
return NO_ERRORS; |
||||||
|
} |
||||||
|
|
||||||
|
return new OAuth2TokenValidatorResult(errors); |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,123 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2002-2018 the original author or authors. |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
package org.springframework.security.oauth2.core; |
||||||
|
|
||||||
|
import java.util.Arrays; |
||||||
|
import java.util.Collections; |
||||||
|
|
||||||
|
import org.junit.Test; |
||||||
|
|
||||||
|
import org.springframework.security.oauth2.core.AbstractOAuth2Token; |
||||||
|
import org.springframework.security.oauth2.core.DelegatingOAuth2TokenValidator; |
||||||
|
import org.springframework.security.oauth2.core.OAuth2Error; |
||||||
|
import org.springframework.security.oauth2.core.OAuth2TokenValidator; |
||||||
|
import org.springframework.security.oauth2.core.OAuth2TokenValidatorResult; |
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat; |
||||||
|
import static org.assertj.core.api.Assertions.assertThatCode; |
||||||
|
import static org.mockito.ArgumentMatchers.any; |
||||||
|
import static org.mockito.Mockito.mock; |
||||||
|
import static org.mockito.Mockito.when; |
||||||
|
|
||||||
|
/** |
||||||
|
* Tests for verifying {@link DelegatingOAuth2TokenValidator} |
||||||
|
* |
||||||
|
* @author Josh Cummings |
||||||
|
*/ |
||||||
|
public class DelegatingOAuth2TokenValidatorTests { |
||||||
|
private static final OAuth2Error DETAIL = new OAuth2Error( |
||||||
|
"error", "description", "uri"); |
||||||
|
|
||||||
|
@Test |
||||||
|
public void validateWhenNoValidatorsConfiguredThenReturnsSuccessfulResult() { |
||||||
|
DelegatingOAuth2TokenValidator<AbstractOAuth2Token> tokenValidator = |
||||||
|
new DelegatingOAuth2TokenValidator<>(Collections.emptyList()); |
||||||
|
AbstractOAuth2Token token = mock(AbstractOAuth2Token.class); |
||||||
|
|
||||||
|
assertThat(tokenValidator.validate(token).hasErrors()).isFalse(); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void validateWhenAnyValidatorFailsThenReturnsFailureResultContainingDetailFromFailingValidator() { |
||||||
|
OAuth2TokenValidator<AbstractOAuth2Token> success = mock(OAuth2TokenValidator.class); |
||||||
|
OAuth2TokenValidator<AbstractOAuth2Token> failure = mock(OAuth2TokenValidator.class); |
||||||
|
|
||||||
|
when(success.validate(any(AbstractOAuth2Token.class))) |
||||||
|
.thenReturn(OAuth2TokenValidatorResult.success()); |
||||||
|
when(failure.validate(any(AbstractOAuth2Token.class))) |
||||||
|
.thenReturn(OAuth2TokenValidatorResult.failure(DETAIL)); |
||||||
|
|
||||||
|
DelegatingOAuth2TokenValidator<AbstractOAuth2Token> tokenValidator = |
||||||
|
new DelegatingOAuth2TokenValidator<>(Arrays.asList(success, failure)); |
||||||
|
AbstractOAuth2Token token = mock(AbstractOAuth2Token.class); |
||||||
|
|
||||||
|
OAuth2TokenValidatorResult result = |
||||||
|
tokenValidator.validate(token); |
||||||
|
|
||||||
|
assertThat(result.hasErrors()).isTrue(); |
||||||
|
assertThat(result.getErrors()).containsExactly(DETAIL); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void validateWhenMultipleValidatorsFailThenReturnsFailureResultContainingAllDetails() { |
||||||
|
OAuth2TokenValidator<AbstractOAuth2Token> firstFailure = mock(OAuth2TokenValidator.class); |
||||||
|
OAuth2TokenValidator<AbstractOAuth2Token> secondFailure = mock(OAuth2TokenValidator.class); |
||||||
|
|
||||||
|
OAuth2Error otherDetail = new OAuth2Error("another-error"); |
||||||
|
|
||||||
|
when(firstFailure.validate(any(AbstractOAuth2Token.class))) |
||||||
|
.thenReturn(OAuth2TokenValidatorResult.failure(DETAIL)); |
||||||
|
when(secondFailure.validate(any(AbstractOAuth2Token.class))) |
||||||
|
.thenReturn(OAuth2TokenValidatorResult.failure(otherDetail)); |
||||||
|
|
||||||
|
DelegatingOAuth2TokenValidator<AbstractOAuth2Token> tokenValidator = |
||||||
|
new DelegatingOAuth2TokenValidator<>(Arrays.asList(firstFailure, secondFailure)); |
||||||
|
AbstractOAuth2Token token = mock(AbstractOAuth2Token.class); |
||||||
|
|
||||||
|
OAuth2TokenValidatorResult result = |
||||||
|
tokenValidator.validate(token); |
||||||
|
|
||||||
|
assertThat(result.hasErrors()).isTrue(); |
||||||
|
assertThat(result.getErrors()).containsExactly(DETAIL, otherDetail); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void validateWhenAllValidatorsSucceedThenReturnsSuccessfulResult() { |
||||||
|
OAuth2TokenValidator<AbstractOAuth2Token> firstSuccess = mock(OAuth2TokenValidator.class); |
||||||
|
OAuth2TokenValidator<AbstractOAuth2Token> secondSuccess = mock(OAuth2TokenValidator.class); |
||||||
|
|
||||||
|
when(firstSuccess.validate(any(AbstractOAuth2Token.class))) |
||||||
|
.thenReturn(OAuth2TokenValidatorResult.success()); |
||||||
|
when(secondSuccess.validate(any(AbstractOAuth2Token.class))) |
||||||
|
.thenReturn(OAuth2TokenValidatorResult.success()); |
||||||
|
|
||||||
|
DelegatingOAuth2TokenValidator<AbstractOAuth2Token> tokenValidator = |
||||||
|
new DelegatingOAuth2TokenValidator<>(Arrays.asList(firstSuccess, secondSuccess)); |
||||||
|
AbstractOAuth2Token token = mock(AbstractOAuth2Token.class); |
||||||
|
|
||||||
|
OAuth2TokenValidatorResult result = |
||||||
|
tokenValidator.validate(token); |
||||||
|
|
||||||
|
assertThat(result.hasErrors()).isFalse(); |
||||||
|
assertThat(result.getErrors()).isEmpty(); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void constructorWhenInvokedWithNullValidatorListThenThrowsIllegalArgumentException() { |
||||||
|
assertThatCode(() -> new DelegatingOAuth2TokenValidator<>(null)) |
||||||
|
.isInstanceOf(IllegalArgumentException.class); |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,55 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2002-2018 the original author or authors. |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
package org.springframework.security.oauth2.core; |
||||||
|
|
||||||
|
import org.junit.Test; |
||||||
|
|
||||||
|
import org.springframework.security.oauth2.core.OAuth2Error; |
||||||
|
import org.springframework.security.oauth2.core.OAuth2TokenValidatorResult; |
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat; |
||||||
|
|
||||||
|
/** |
||||||
|
* Tests for verifying {@link OAuth2TokenValidatorResult} |
||||||
|
* |
||||||
|
* @author Josh Cummings |
||||||
|
*/ |
||||||
|
public class OAuth2TokenValidatorResultTests { |
||||||
|
private static final OAuth2Error DETAIL = new OAuth2Error( |
||||||
|
"error", "description", "uri"); |
||||||
|
|
||||||
|
@Test |
||||||
|
public void successWhenInvokedThenReturnsSuccessfulResult() { |
||||||
|
OAuth2TokenValidatorResult success = OAuth2TokenValidatorResult.success(); |
||||||
|
assertThat(success.hasErrors()).isFalse(); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void failureWhenInvokedWithDetailReturnsFailureResultIncludingDetail() { |
||||||
|
OAuth2TokenValidatorResult failure = OAuth2TokenValidatorResult.failure(DETAIL); |
||||||
|
|
||||||
|
assertThat(failure.hasErrors()).isTrue(); |
||||||
|
assertThat(failure.getErrors()).containsExactly(DETAIL); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void failureWhenInvokedWithMultipleDetailsReturnsFailureResultIncludingAll() { |
||||||
|
OAuth2TokenValidatorResult failure = OAuth2TokenValidatorResult.failure(DETAIL, DETAIL); |
||||||
|
|
||||||
|
assertThat(failure.hasErrors()).isTrue(); |
||||||
|
assertThat(failure.getErrors()).containsExactly(DETAIL, DETAIL); |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,72 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2002-2018 the original author or authors. |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
package org.springframework.security.oauth2.jwt; |
||||||
|
|
||||||
|
import java.net.MalformedURLException; |
||||||
|
import java.net.URL; |
||||||
|
|
||||||
|
import org.springframework.security.oauth2.core.OAuth2Error; |
||||||
|
import org.springframework.security.oauth2.core.OAuth2ErrorCodes; |
||||||
|
import org.springframework.security.oauth2.core.OAuth2TokenValidator; |
||||||
|
import org.springframework.security.oauth2.core.OAuth2TokenValidatorResult; |
||||||
|
import org.springframework.util.Assert; |
||||||
|
|
||||||
|
/** |
||||||
|
* Validates the "iss" claim in a {@link Jwt}, that is matches a configured value |
||||||
|
* |
||||||
|
* @author Josh Cummings |
||||||
|
* @since 5.1 |
||||||
|
*/ |
||||||
|
public final class JwtIssuerValidator implements OAuth2TokenValidator<Jwt> { |
||||||
|
private static OAuth2Error INVALID_ISSUER = |
||||||
|
new OAuth2Error( |
||||||
|
OAuth2ErrorCodes.INVALID_REQUEST, |
||||||
|
"This iss claim is not equal to the configured issuer", |
||||||
|
"https://tools.ietf.org/html/rfc6750#section-3.1"); |
||||||
|
|
||||||
|
private final URL issuer; |
||||||
|
|
||||||
|
/** |
||||||
|
* Constructs a {@link JwtIssuerValidator} using the provided parameters |
||||||
|
* |
||||||
|
* @param issuer - The issuer that each {@link Jwt} should have. |
||||||
|
*/ |
||||||
|
public JwtIssuerValidator(String issuer) { |
||||||
|
Assert.notNull(issuer, "issuer cannot be null"); |
||||||
|
|
||||||
|
try { |
||||||
|
this.issuer = new URL(issuer); |
||||||
|
} catch (MalformedURLException ex) { |
||||||
|
throw new IllegalArgumentException( |
||||||
|
"Invalid Issuer URL " + issuer + " : " + ex.getMessage(), |
||||||
|
ex); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* {@inheritDoc} |
||||||
|
*/ |
||||||
|
@Override |
||||||
|
public OAuth2TokenValidatorResult validate(Jwt token) { |
||||||
|
Assert.notNull(token, "token cannot be null"); |
||||||
|
|
||||||
|
if (this.issuer.equals(token.getIssuer())) { |
||||||
|
return OAuth2TokenValidatorResult.success(); |
||||||
|
} else { |
||||||
|
return OAuth2TokenValidatorResult.failure(INVALID_ISSUER); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,109 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2002-2018 the original author or authors. |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
package org.springframework.security.oauth2.jwt; |
||||||
|
|
||||||
|
import java.time.Clock; |
||||||
|
import java.time.Duration; |
||||||
|
import java.time.Instant; |
||||||
|
import java.time.temporal.ChronoUnit; |
||||||
|
|
||||||
|
import org.springframework.security.oauth2.core.OAuth2Error; |
||||||
|
import org.springframework.security.oauth2.core.OAuth2ErrorCodes; |
||||||
|
import org.springframework.security.oauth2.core.OAuth2TokenValidator; |
||||||
|
import org.springframework.security.oauth2.core.OAuth2TokenValidatorResult; |
||||||
|
import org.springframework.security.oauth2.jwt.Jwt; |
||||||
|
import org.springframework.util.Assert; |
||||||
|
|
||||||
|
/** |
||||||
|
* An implementation of {@see OAuth2TokenValidator} for verifying claims in a Jwt-based access token |
||||||
|
* |
||||||
|
* <p> |
||||||
|
* Because clocks can differ between the Jwt source, say the Authorization Server, and its destination, say the |
||||||
|
* Resource Server, there is a default clock leeway exercised when deciding if the current time is within the Jwt's |
||||||
|
* specified operating window |
||||||
|
* |
||||||
|
* @author Josh Cummings |
||||||
|
* @since 5.1 |
||||||
|
* @see Jwt |
||||||
|
* @see OAuth2TokenValidator |
||||||
|
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc7519">JSON Web Token (JWT)</a> |
||||||
|
*/ |
||||||
|
public final class JwtTimestampValidator implements OAuth2TokenValidator<Jwt> { |
||||||
|
private static final Duration DEFAULT_MAX_CLOCK_SKEW = Duration.of(60, ChronoUnit.SECONDS); |
||||||
|
|
||||||
|
private final Duration maxClockSkew; |
||||||
|
|
||||||
|
private Clock clock = Clock.systemUTC(); |
||||||
|
|
||||||
|
/** |
||||||
|
* A basic instance with no custom verification and the default max clock skew |
||||||
|
*/ |
||||||
|
public JwtTimestampValidator() { |
||||||
|
this(DEFAULT_MAX_CLOCK_SKEW); |
||||||
|
} |
||||||
|
|
||||||
|
public JwtTimestampValidator(Duration maxClockSkew) { |
||||||
|
Assert.notNull(maxClockSkew, "maxClockSkew cannot be null"); |
||||||
|
|
||||||
|
this.maxClockSkew = maxClockSkew; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* {@inheritDoc} |
||||||
|
*/ |
||||||
|
@Override |
||||||
|
public OAuth2TokenValidatorResult validate(Jwt jwt) { |
||||||
|
Assert.notNull(jwt, "jwt cannot be null"); |
||||||
|
|
||||||
|
Instant expiry = jwt.getExpiresAt(); |
||||||
|
|
||||||
|
if (expiry != null) { |
||||||
|
if (Instant.now(this.clock).minus(maxClockSkew).isAfter(expiry)) { |
||||||
|
OAuth2Error error = new OAuth2Error( |
||||||
|
OAuth2ErrorCodes.INVALID_REQUEST, |
||||||
|
String.format("Jwt expired at %s", jwt.getExpiresAt()), |
||||||
|
"https://tools.ietf.org/html/rfc6750#section-3.1"); |
||||||
|
return OAuth2TokenValidatorResult.failure(error); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
Instant notBefore = jwt.getNotBefore(); |
||||||
|
|
||||||
|
if (notBefore != null) { |
||||||
|
if (Instant.now(this.clock).plus(maxClockSkew).isBefore(notBefore)) { |
||||||
|
OAuth2Error error = new OAuth2Error( |
||||||
|
OAuth2ErrorCodes.INVALID_REQUEST, |
||||||
|
String.format("Jwt used before %s", jwt.getNotBefore()), |
||||||
|
"https://tools.ietf.org/html/rfc6750#section-3.1"); |
||||||
|
return OAuth2TokenValidatorResult.failure(error); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return OAuth2TokenValidatorResult.success(); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* ' |
||||||
|
* Use this {@link Clock} with {@link Instant#now()} for assessing |
||||||
|
* timestamp validity |
||||||
|
* |
||||||
|
* @param clock |
||||||
|
*/ |
||||||
|
public void setClock(Clock clock) { |
||||||
|
Assert.notNull(clock, "clock cannot be null"); |
||||||
|
this.clock = clock; |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,68 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2002-2018 the original author or authors. |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
package org.springframework.security.oauth2.jwt; |
||||||
|
|
||||||
|
import java.util.ArrayList; |
||||||
|
import java.util.Collection; |
||||||
|
|
||||||
|
import org.springframework.security.oauth2.core.OAuth2Error; |
||||||
|
import org.springframework.security.oauth2.core.OAuth2TokenValidatorResult; |
||||||
|
import org.springframework.util.Assert; |
||||||
|
|
||||||
|
/** |
||||||
|
* An exception that results from an unsuccessful |
||||||
|
* {@link OAuth2TokenValidatorResult} |
||||||
|
* |
||||||
|
* @author Josh Cummings |
||||||
|
* @since 5.1 |
||||||
|
*/ |
||||||
|
public class JwtValidationException extends JwtException { |
||||||
|
private final Collection<OAuth2Error> errors; |
||||||
|
|
||||||
|
/** |
||||||
|
* Constructs a {@link JwtValidationException} using the provided parameters |
||||||
|
* |
||||||
|
* While each {@link OAuth2Error} does contain an error description, this constructor |
||||||
|
* can take an overarching description that encapsulates the composition of failures |
||||||
|
* |
||||||
|
* That said, it is appropriate to pass one of the messages from the error list in as |
||||||
|
* the exception description, for example: |
||||||
|
* |
||||||
|
* <pre> |
||||||
|
* if ( result.hasErrors() ) { |
||||||
|
* Collection<OAuth2Error> errors = result.getErrors(); |
||||||
|
* throw new JwtValidationException(errors.iterator().next().getDescription(), errors); |
||||||
|
* } |
||||||
|
* </pre> |
||||||
|
* |
||||||
|
* @param message - the exception message |
||||||
|
* @param errors - a list of {@link OAuth2Error}s with extra detail about the validation result |
||||||
|
*/ |
||||||
|
public JwtValidationException(String message, Collection<OAuth2Error> errors) { |
||||||
|
super(message); |
||||||
|
|
||||||
|
Assert.notEmpty(errors, "errors cannot be empty"); |
||||||
|
this.errors = new ArrayList<>(errors); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Return the list of {@link OAuth2Error}s associated with this exception |
||||||
|
* @return the list of {@link OAuth2Error}s associated with this exception |
||||||
|
*/ |
||||||
|
public Collection<OAuth2Error> getErrors() { |
||||||
|
return this.errors; |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,46 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2002-2018 the original author or authors. |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
package org.springframework.security.oauth2.jwt; |
||||||
|
|
||||||
|
import java.util.ArrayList; |
||||||
|
import java.util.Arrays; |
||||||
|
import java.util.Collection; |
||||||
|
|
||||||
|
import org.springframework.security.oauth2.core.DelegatingOAuth2TokenValidator; |
||||||
|
import org.springframework.security.oauth2.core.OAuth2TokenValidator; |
||||||
|
|
||||||
|
/** |
||||||
|
* @author Josh Cummings |
||||||
|
* @since 5.1 |
||||||
|
*/ |
||||||
|
public final class JwtValidators { |
||||||
|
|
||||||
|
/** |
||||||
|
* Create a {@link Jwt} Validator that contains all standard validators as well as |
||||||
|
* any supplied in the parameter list. |
||||||
|
* |
||||||
|
* @param jwtValidators - additional validators to include in the delegating validator |
||||||
|
* @return - a delegating validator containing all standard validators as well as any supplied |
||||||
|
*/ |
||||||
|
public static OAuth2TokenValidator<Jwt> createDelegatingJwtValidator(OAuth2TokenValidator<Jwt>... jwtValidators) { |
||||||
|
Collection<OAuth2TokenValidator<Jwt>> validators = new ArrayList<>(); |
||||||
|
validators.add(new JwtTimestampValidator()); |
||||||
|
validators.addAll(Arrays.asList(jwtValidators)); |
||||||
|
return new DelegatingOAuth2TokenValidator<>(validators); |
||||||
|
} |
||||||
|
|
||||||
|
private JwtValidators() {} |
||||||
|
} |
||||||
@ -0,0 +1,92 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2002-2018 the original author or authors. |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
package org.springframework.security.oauth2.jwt; |
||||||
|
|
||||||
|
import java.time.Instant; |
||||||
|
import java.util.Collections; |
||||||
|
import java.util.Map; |
||||||
|
|
||||||
|
import org.junit.Test; |
||||||
|
|
||||||
|
import org.springframework.security.oauth2.core.OAuth2TokenValidatorResult; |
||||||
|
import org.springframework.security.oauth2.jose.jws.JwsAlgorithms; |
||||||
|
import org.springframework.security.oauth2.jwt.Jwt; |
||||||
|
import org.springframework.security.oauth2.jwt.JwtClaimNames; |
||||||
|
import org.springframework.security.oauth2.jwt.JwtIssuerValidator; |
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat; |
||||||
|
import static org.assertj.core.api.Assertions.assertThatCode; |
||||||
|
|
||||||
|
/** |
||||||
|
* @author Josh Cummings |
||||||
|
* @since 5.1 |
||||||
|
*/ |
||||||
|
public class JwtIssuerValidatorTests { |
||||||
|
private static final String MOCK_TOKEN = "token"; |
||||||
|
private static final Instant MOCK_ISSUED_AT = Instant.MIN; |
||||||
|
private static final Instant MOCK_EXPIRES_AT = Instant.MAX; |
||||||
|
private static final Map<String, Object> MOCK_HEADERS = |
||||||
|
Collections.singletonMap("alg", JwsAlgorithms.RS256); |
||||||
|
|
||||||
|
private static final String ISSUER = "https://issuer"; |
||||||
|
|
||||||
|
private final JwtIssuerValidator validator = new JwtIssuerValidator(ISSUER); |
||||||
|
|
||||||
|
@Test |
||||||
|
public void validateWhenIssuerMatchesThenReturnsSuccess() { |
||||||
|
Jwt jwt = new Jwt( |
||||||
|
MOCK_TOKEN, |
||||||
|
MOCK_ISSUED_AT, |
||||||
|
MOCK_EXPIRES_AT, |
||||||
|
MOCK_HEADERS, |
||||||
|
Collections.singletonMap("iss", ISSUER)); |
||||||
|
|
||||||
|
assertThat(this.validator.validate(jwt)) |
||||||
|
.isEqualTo(OAuth2TokenValidatorResult.success()); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void validateWhenIssuerMismatchesThenReturnsError() { |
||||||
|
Jwt jwt = new Jwt( |
||||||
|
MOCK_TOKEN, |
||||||
|
MOCK_ISSUED_AT, |
||||||
|
MOCK_EXPIRES_AT, |
||||||
|
MOCK_HEADERS, |
||||||
|
Collections.singletonMap(JwtClaimNames.ISS, "https://other")); |
||||||
|
|
||||||
|
OAuth2TokenValidatorResult result = this.validator.validate(jwt); |
||||||
|
|
||||||
|
assertThat(result.getErrors()).isNotEmpty(); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void validateWhenJwtIsNullThenThrowsIllegalArgumentException() { |
||||||
|
assertThatCode(() -> this.validator.validate(null)) |
||||||
|
.isInstanceOf(IllegalArgumentException.class); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void constructorWhenMalformedIssuerIsGivenThenThrowsIllegalArgumentException() { |
||||||
|
assertThatCode(() -> new JwtIssuerValidator("issuer")) |
||||||
|
.isInstanceOf(IllegalArgumentException.class); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void constructorWhenNullIssuerIsGivenThenThrowsIllegalArgumentException() { |
||||||
|
assertThatCode(() -> new JwtIssuerValidator(null)) |
||||||
|
.isInstanceOf(IllegalArgumentException.class); |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,230 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2002-2018 the original author or authors. |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
package org.springframework.security.oauth2.jwt; |
||||||
|
|
||||||
|
import java.time.Clock; |
||||||
|
import java.time.Duration; |
||||||
|
import java.time.Instant; |
||||||
|
import java.time.ZoneId; |
||||||
|
import java.util.Collection; |
||||||
|
import java.util.Collections; |
||||||
|
import java.util.Map; |
||||||
|
import java.util.stream.Collectors; |
||||||
|
|
||||||
|
import org.junit.Test; |
||||||
|
|
||||||
|
import org.springframework.security.oauth2.core.OAuth2Error; |
||||||
|
import org.springframework.security.oauth2.core.OAuth2TokenValidatorResult; |
||||||
|
import org.springframework.security.oauth2.jose.jws.JwsAlgorithms; |
||||||
|
import org.springframework.security.oauth2.jwt.Jwt; |
||||||
|
import org.springframework.security.oauth2.jwt.JwtClaimNames; |
||||||
|
import org.springframework.security.oauth2.jwt.JwtTimestampValidator; |
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat; |
||||||
|
import static org.assertj.core.api.Assertions.assertThatCode; |
||||||
|
|
||||||
|
/** |
||||||
|
* Tests verifying {@link JwtTimestampValidator} |
||||||
|
* |
||||||
|
* @author Josh Cummings |
||||||
|
*/ |
||||||
|
public class JwtTimestampValidatorTests { |
||||||
|
private static final Clock MOCK_NOW = Clock.fixed(Instant.ofEpochMilli(0), ZoneId.systemDefault()); |
||||||
|
private static final String MOCK_TOKEN_VALUE = "token"; |
||||||
|
private static final Instant MOCK_ISSUED_AT = Instant.MIN; |
||||||
|
private static final Map<String, Object> MOCK_HEADER = Collections.singletonMap("alg", JwsAlgorithms.RS256); |
||||||
|
private static final Map<String, Object> MOCK_CLAIM_SET = Collections.singletonMap("some", "claim"); |
||||||
|
|
||||||
|
@Test |
||||||
|
public void validateWhenJwtIsExpiredThenErrorMessageIndicatesExpirationTime() { |
||||||
|
Instant oneHourAgo = Instant.now().minusSeconds(3600); |
||||||
|
|
||||||
|
Jwt jwt = new Jwt( |
||||||
|
MOCK_TOKEN_VALUE, |
||||||
|
MOCK_ISSUED_AT, |
||||||
|
oneHourAgo, |
||||||
|
MOCK_HEADER, |
||||||
|
MOCK_CLAIM_SET); |
||||||
|
|
||||||
|
JwtTimestampValidator jwtValidator = new JwtTimestampValidator(); |
||||||
|
|
||||||
|
Collection<OAuth2Error> details = jwtValidator.validate(jwt).getErrors(); |
||||||
|
Collection<String> messages = details.stream().map(OAuth2Error::getDescription).collect(Collectors.toList()); |
||||||
|
|
||||||
|
assertThat(messages).contains("Jwt expired at " + oneHourAgo); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void validateWhenJwtIsTooEarlyThenErrorMessageIndicatesNotBeforeTime() { |
||||||
|
Instant oneHourFromNow = Instant.now().plusSeconds(3600); |
||||||
|
|
||||||
|
Jwt jwt = new Jwt( |
||||||
|
MOCK_TOKEN_VALUE, |
||||||
|
MOCK_ISSUED_AT, |
||||||
|
null, |
||||||
|
MOCK_HEADER, |
||||||
|
Collections.singletonMap(JwtClaimNames.NBF, oneHourFromNow)); |
||||||
|
|
||||||
|
JwtTimestampValidator jwtValidator = new JwtTimestampValidator(); |
||||||
|
|
||||||
|
Collection<OAuth2Error> details = jwtValidator.validate(jwt).getErrors(); |
||||||
|
Collection<String> messages = details.stream().map(OAuth2Error::getDescription).collect(Collectors.toList()); |
||||||
|
|
||||||
|
assertThat(messages).contains("Jwt used before " + oneHourFromNow); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void validateWhenConfiguredWithClockSkewThenValidatesUsingThatSkew() { |
||||||
|
Duration oneDayOff = Duration.ofDays(1); |
||||||
|
JwtTimestampValidator jwtValidator = new JwtTimestampValidator(oneDayOff); |
||||||
|
|
||||||
|
Instant now = Instant.now(); |
||||||
|
Instant almostOneDayAgo = now.minus(oneDayOff).plusSeconds(10); |
||||||
|
Instant almostOneDayFromNow = now.plus(oneDayOff).minusSeconds(10); |
||||||
|
Instant justOverOneDayAgo = now.minus(oneDayOff).minusSeconds(10); |
||||||
|
Instant justOverOneDayFromNow = now.plus(oneDayOff).plusSeconds(10); |
||||||
|
|
||||||
|
Jwt jwt = new Jwt( |
||||||
|
MOCK_TOKEN_VALUE, |
||||||
|
MOCK_ISSUED_AT, |
||||||
|
almostOneDayAgo, |
||||||
|
MOCK_HEADER, |
||||||
|
Collections.singletonMap(JwtClaimNames.NBF, almostOneDayFromNow)); |
||||||
|
|
||||||
|
assertThat(jwtValidator.validate(jwt).hasErrors()).isFalse(); |
||||||
|
|
||||||
|
jwt = new Jwt( |
||||||
|
MOCK_TOKEN_VALUE, |
||||||
|
MOCK_ISSUED_AT, |
||||||
|
justOverOneDayAgo, |
||||||
|
MOCK_HEADER, |
||||||
|
MOCK_CLAIM_SET); |
||||||
|
|
||||||
|
OAuth2TokenValidatorResult result = jwtValidator.validate(jwt); |
||||||
|
Collection<String> messages = |
||||||
|
result.getErrors().stream().map(OAuth2Error::getDescription).collect(Collectors.toList()); |
||||||
|
|
||||||
|
assertThat(result.hasErrors()).isTrue(); |
||||||
|
assertThat(messages).contains("Jwt expired at " + justOverOneDayAgo); |
||||||
|
|
||||||
|
jwt = new Jwt( |
||||||
|
MOCK_TOKEN_VALUE, |
||||||
|
MOCK_ISSUED_AT, |
||||||
|
null, |
||||||
|
MOCK_HEADER, |
||||||
|
Collections.singletonMap(JwtClaimNames.NBF, justOverOneDayFromNow)); |
||||||
|
|
||||||
|
result = jwtValidator.validate(jwt); |
||||||
|
messages = |
||||||
|
result.getErrors().stream().map(OAuth2Error::getDescription).collect(Collectors.toList()); |
||||||
|
|
||||||
|
assertThat(result.hasErrors()).isTrue(); |
||||||
|
assertThat(messages).contains("Jwt used before " + justOverOneDayFromNow); |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void validateWhenConfiguredWithFixedClockThenValidatesUsingFixedTime() { |
||||||
|
Jwt jwt = new Jwt( |
||||||
|
MOCK_TOKEN_VALUE, |
||||||
|
MOCK_ISSUED_AT, |
||||||
|
Instant.now(MOCK_NOW), |
||||||
|
MOCK_HEADER, |
||||||
|
Collections.singletonMap("some", "claim")); |
||||||
|
|
||||||
|
JwtTimestampValidator jwtValidator = new JwtTimestampValidator(Duration.ofNanos(0)); |
||||||
|
jwtValidator.setClock(MOCK_NOW); |
||||||
|
|
||||||
|
assertThat(jwtValidator.validate(jwt).hasErrors()).isFalse(); |
||||||
|
|
||||||
|
jwt = new Jwt( |
||||||
|
MOCK_TOKEN_VALUE, |
||||||
|
MOCK_ISSUED_AT, |
||||||
|
null, |
||||||
|
MOCK_HEADER, |
||||||
|
Collections.singletonMap(JwtClaimNames.NBF, Instant.now(MOCK_NOW))); |
||||||
|
|
||||||
|
assertThat(jwtValidator.validate(jwt).hasErrors()).isFalse(); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void validateWhenNeitherExpiryNorNotBeforeIsSpecifiedThenReturnsSuccessfulResult() { |
||||||
|
Jwt jwt = new Jwt( |
||||||
|
MOCK_TOKEN_VALUE, |
||||||
|
MOCK_ISSUED_AT, |
||||||
|
null, |
||||||
|
MOCK_HEADER, |
||||||
|
MOCK_CLAIM_SET); |
||||||
|
|
||||||
|
JwtTimestampValidator jwtValidator = new JwtTimestampValidator(); |
||||||
|
assertThat(jwtValidator.validate(jwt).hasErrors()).isFalse(); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void validateWhenNotBeforeIsValidAndExpiryIsNotSpecifiedThenReturnsSuccessfulResult() { |
||||||
|
Jwt jwt = new Jwt( |
||||||
|
MOCK_TOKEN_VALUE, |
||||||
|
MOCK_ISSUED_AT, |
||||||
|
null, |
||||||
|
MOCK_HEADER, |
||||||
|
Collections.singletonMap(JwtClaimNames.NBF, Instant.MIN)); |
||||||
|
|
||||||
|
JwtTimestampValidator jwtValidator = new JwtTimestampValidator(); |
||||||
|
assertThat(jwtValidator.validate(jwt).hasErrors()).isFalse(); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void validateWhenExpiryIsValidAndNotBeforeIsNotSpecifiedThenReturnsSuccessfulResult() { |
||||||
|
Jwt jwt = new Jwt( |
||||||
|
MOCK_TOKEN_VALUE, |
||||||
|
MOCK_ISSUED_AT, |
||||||
|
Instant.MAX, |
||||||
|
MOCK_HEADER, |
||||||
|
MOCK_CLAIM_SET); |
||||||
|
|
||||||
|
JwtTimestampValidator jwtValidator = new JwtTimestampValidator(); |
||||||
|
assertThat(jwtValidator.validate(jwt).hasErrors()).isFalse(); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void validateWhenBothExpiryAndNotBeforeAreValidThenReturnsSuccessfulResult() { |
||||||
|
Jwt jwt = new Jwt( |
||||||
|
MOCK_TOKEN_VALUE, |
||||||
|
MOCK_ISSUED_AT, |
||||||
|
Instant.now(MOCK_NOW), |
||||||
|
MOCK_HEADER, |
||||||
|
Collections.singletonMap(JwtClaimNames.NBF, Instant.now(MOCK_NOW))); |
||||||
|
|
||||||
|
JwtTimestampValidator jwtValidator = new JwtTimestampValidator(Duration.ofNanos(0)); |
||||||
|
jwtValidator.setClock(MOCK_NOW); |
||||||
|
|
||||||
|
assertThat(jwtValidator.validate(jwt).hasErrors()).isFalse(); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void setClockWhenInvokedWithNullThenThrowsIllegalArgumentException() { |
||||||
|
JwtTimestampValidator jwtValidator = new JwtTimestampValidator(); |
||||||
|
|
||||||
|
assertThatCode(() -> jwtValidator.setClock(null)) |
||||||
|
.isInstanceOf(IllegalArgumentException.class); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void constructorWhenInvokedWithNullDurationThenThrowsIllegalArgumentException() { |
||||||
|
assertThatCode(() -> new JwtTimestampValidator(null)) |
||||||
|
.isInstanceOf(IllegalArgumentException.class); |
||||||
|
} |
||||||
|
} |
||||||
Loading…
Reference in new issue