Jwt Claim Validation

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-5133
This commit is contained in:
Josh Cummings
2018-07-24 11:47:30 -06:00
committed by Rob Winch
parent c6ea447cc0
commit 7c524aa0c8
16 changed files with 1225 additions and 25 deletions

View File

@@ -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);
}
}

View File

@@ -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 &quot;verifying&quot;
* 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);
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}