Browse Source

Merge 450a6cacc2 into 14d469cec1

pull/18506/merge
Iain Henderson 6 days ago committed by GitHub
parent
commit
36b080240d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 95
      oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/ReactiveDelegatingOAuth2TokenValidator.java
  2. 37
      oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/ReactiveOAuth2TokenValidator.java
  3. 46
      oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/ReactiveWrappingOAuth2TokenValidator.java
  4. 118
      oauth2/oauth2-core/src/test/java/org/springframework/security/oauth2/core/ReactiveDelegatingOAuth2TokenValidatorTests.java
  5. 57
      oauth2/oauth2-core/src/test/java/org/springframework/security/oauth2/core/ReactiveWrappingOAuth2TokenValidatorTests.java
  6. 23
      oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/JwtValidators.java
  7. 223
      oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/NimbusReactiveJwtDecoder.java
  8. 131
      oauth2/oauth2-jose/src/test/java/org/springframework/security/oauth2/jwt/NimbusReactiveJwtDecoderTests.java

95
oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/ReactiveDelegatingOAuth2TokenValidator.java

@ -0,0 +1,95 @@ @@ -0,0 +1,95 @@
/*
* Copyright 2025-present 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
*
* https://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 reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.springframework.util.Assert;
/**
* A reactive composite validator
*
* @param <T> the type of {@link OAuth2Token} this validator validates
* @author Josh Cummings
* @author Iain Henderson
*/
public final class ReactiveDelegatingOAuth2TokenValidator<T extends OAuth2Token> implements ReactiveOAuth2TokenValidator<T> {
private final Collection<OAuth2TokenValidator<T>> tokenValidators;
private final Collection<ReactiveOAuth2TokenValidator<T>> reactiveTokenValidators;
/**
* Constructs a {@code ReactiveDelegatingOAuth2TokenValidator} using the provided validators.
* @param tokenValidators the {@link Collection} of {@link OAuth2TokenValidator}s to
* use
* @param reactiveTokenValidators the {@link Collection} of {@link ReactiveOAuth2TokenValidator}s to
* use
*/
public ReactiveDelegatingOAuth2TokenValidator(Collection<OAuth2TokenValidator<T>> tokenValidators,
Collection<ReactiveOAuth2TokenValidator<T>> reactiveTokenValidators) {
Assert.notNull(tokenValidators, "tokenValidators cannot be null");
Assert.notNull(reactiveTokenValidators, "reactiveTokenValidators cannot be null");
this.tokenValidators = new ArrayList<>(tokenValidators);
this.reactiveTokenValidators = new ArrayList<>(reactiveTokenValidators);
}
/**
* Constructs a {@code ReactiveDelegatingOAuth2TokenValidator} using the provided validators.
* @param reactiveTokenValidators the {@link Collection} of {@link ReactiveOAuth2TokenValidator}s to
* use
*/
public ReactiveDelegatingOAuth2TokenValidator(Collection<ReactiveOAuth2TokenValidator<T>> reactiveTokenValidators) {
this(Collections.emptyList(), reactiveTokenValidators);
}
/**
* Constructs a {@code ReactiveDelegatingOAuth2TokenValidator} using the provided validators.
* @param tokenValidators the collection of {@link OAuth2TokenValidator}s to use
*/
@SafeVarargs
public ReactiveDelegatingOAuth2TokenValidator(OAuth2TokenValidator<T>... tokenValidators) {
this(Arrays.asList(tokenValidators), Collections.emptyList());
}
/**
* Constructs a {@code ReactiveDelegatingOAuth2TokenValidator} using the provided validators.
* @param reactiveTokenValidators the collection of {@link ReactiveOAuth2TokenValidator}s to use
*/
@SafeVarargs
public ReactiveDelegatingOAuth2TokenValidator(ReactiveOAuth2TokenValidator<T>... reactiveTokenValidators) {
this(Arrays.asList(reactiveTokenValidators));
}
@Override
public Mono<OAuth2TokenValidatorResult> validate(T token) {
return Flux.fromIterable(this.tokenValidators)
.map(validator -> validator.validate(token))
.mergeWith(Flux.fromIterable(reactiveTokenValidators)
.flatMap(validator -> validator.validate(token)))
.map(OAuth2TokenValidatorResult::getErrors)
.flatMap(Flux::fromIterable)
.collectList()
.map(OAuth2TokenValidatorResult::failure);
}
}

37
oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/ReactiveOAuth2TokenValidator.java

@ -0,0 +1,37 @@ @@ -0,0 +1,37 @@
/*
* Copyright 2026-present 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
*
* https://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 reactor.core.publisher.Mono;
/**
* 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 Iain Henderson
*/
@FunctionalInterface
public interface ReactiveOAuth2TokenValidator<T extends OAuth2Token> {
/**
* Verify the validity and/or constraints of the provided OAuth 2.0 Token.
* @param token an OAuth 2.0 token
* @return Mono<OAuth2TokenValidationResult> the success or failure detail of the validation
*/
Mono<OAuth2TokenValidatorResult> validate(T token);
}

46
oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/ReactiveWrappingOAuth2TokenValidator.java

@ -0,0 +1,46 @@ @@ -0,0 +1,46 @@
/*
* Copyright 2026-present 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
*
* https://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 reactor.core.publisher.Mono;
import org.springframework.util.Assert;
/**
* A reactive wrapper for synchronous validators
*
* @param <T> the type of {@link OAuth2Token} this validator validates
* @author Iain Henderson
*/
public final class ReactiveWrappingOAuth2TokenValidator<T extends OAuth2Token> implements ReactiveOAuth2TokenValidator<T> {
private final OAuth2TokenValidator<T> tokenValidator;
/**
* Constructs a {@code ReactiveWrappingOAuth2TokenValidator} using the provided validator.
* @param tokenValidator the {@link OAuth2TokenValidator}s to use
*/
public ReactiveWrappingOAuth2TokenValidator(OAuth2TokenValidator<T> tokenValidator) {
Assert.notNull(tokenValidator, "tokenValidator cannot be null");
this.tokenValidator = tokenValidator;
}
@Override
public Mono<OAuth2TokenValidatorResult> validate(T token) {
return Mono.just(tokenValidator.validate(token));
}
}

118
oauth2/oauth2-core/src/test/java/org/springframework/security/oauth2/core/ReactiveDelegatingOAuth2TokenValidatorTests.java

@ -0,0 +1,118 @@ @@ -0,0 +1,118 @@
/*
* Copyright 2026-present 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
*
* https://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.jupiter.api.Test;
import reactor.core.publisher.Mono;
import java.util.Arrays;
import java.util.Collection;
import static java.util.Collections.emptyList;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.*;
/**
* Tests for verifying {@link ReactiveDelegatingOAuth2TokenValidator}
*
* @author Josh Cummings
* @author Iain Henderson
*/
public class ReactiveDelegatingOAuth2TokenValidatorTests {
private static final OAuth2Error DETAIL = new OAuth2Error("error", "description", "uri");
@Test
public void validateWhenNoValidatorsConfiguredThenReturnsSuccessfulResult() {
ReactiveDelegatingOAuth2TokenValidator<OAuth2Token> tokenValidator =
new ReactiveDelegatingOAuth2TokenValidator<>(emptyList());
OAuth2Token token = mock(OAuth2Token.class);
assertThat(tokenValidator.validate(token).block().hasErrors()).isFalse();
}
@Test
public void validateWhenAnyValidatorFailsThenReturnsFailureResultContainingDetailFromFailingValidator() {
OAuth2TokenValidator<OAuth2Token> success = mock(OAuth2TokenValidator.class);
OAuth2TokenValidator<OAuth2Token> failure = mock(OAuth2TokenValidator.class);
given(success.validate(any(OAuth2Token.class))).willReturn(OAuth2TokenValidatorResult.success());
given(failure.validate(any(OAuth2Token.class))).willReturn(OAuth2TokenValidatorResult.failure(DETAIL));
ReactiveDelegatingOAuth2TokenValidator<OAuth2Token> tokenValidator = new ReactiveDelegatingOAuth2TokenValidator<>(
success, failure);
OAuth2Token token = mock(OAuth2Token.class);
OAuth2TokenValidatorResult result = tokenValidator.validate(token).block();
assertThat(result).isNotNull();
assertThat(result.hasErrors()).isTrue();
assertThat(result.getErrors()).containsExactly(DETAIL);
}
@Test
public void validateWhenMultipleValidatorsFailThenReturnsFailureResultContainingAllDetails() {
OAuth2TokenValidator<OAuth2Token> firstFailure = mock(OAuth2TokenValidator.class);
OAuth2TokenValidator<OAuth2Token> secondFailure = mock(OAuth2TokenValidator.class);
OAuth2Error otherDetail = new OAuth2Error("another-error");
given(firstFailure.validate(any(OAuth2Token.class))).willReturn(OAuth2TokenValidatorResult.failure(DETAIL));
given(secondFailure.validate(any(OAuth2Token.class)))
.willReturn(OAuth2TokenValidatorResult.failure(otherDetail));
ReactiveDelegatingOAuth2TokenValidator<OAuth2Token> tokenValidator = new ReactiveDelegatingOAuth2TokenValidator<>(firstFailure,
secondFailure);
OAuth2Token token = mock(OAuth2Token.class);
OAuth2TokenValidatorResult result = tokenValidator.validate(token).block();
assertThat(result.hasErrors()).isTrue();
assertThat(result.getErrors()).containsExactly(DETAIL, otherDetail);
}
@Test
public void validateWhenAllValidatorsSucceedThenReturnsSuccessfulResult() {
OAuth2TokenValidator<OAuth2Token> firstSuccess = mock(OAuth2TokenValidator.class);
OAuth2TokenValidator<OAuth2Token> secondSuccess = mock(OAuth2TokenValidator.class);
given(firstSuccess.validate(any(OAuth2Token.class))).willReturn(OAuth2TokenValidatorResult.success());
given(secondSuccess.validate(any(OAuth2Token.class))).willReturn(OAuth2TokenValidatorResult.success());
ReactiveDelegatingOAuth2TokenValidator<OAuth2Token> tokenValidator =
new ReactiveDelegatingOAuth2TokenValidator<>(firstSuccess, secondSuccess);
OAuth2Token token = mock(OAuth2Token.class);
OAuth2TokenValidatorResult result = tokenValidator.validate(token).block();
assertThat(result.hasErrors()).isFalse();
assertThat(result.getErrors()).isEmpty();
}
@Test
public void constructorWhenInvokedWithNullValidatorListThenThrowsIllegalArgumentException() {
assertThatIllegalArgumentException().isThrownBy(
() -> new ReactiveDelegatingOAuth2TokenValidator<>((Collection<ReactiveOAuth2TokenValidator<OAuth2Token>>) null));
}
@Test
public void constructorsWhenInvokedWithSameInputsThenResultInSameOutputs() {
ReactiveOAuth2TokenValidator<OAuth2Token> firstSuccess = mock(ReactiveOAuth2TokenValidator.class);
ReactiveOAuth2TokenValidator<OAuth2Token> secondSuccess = mock(ReactiveOAuth2TokenValidator.class);
given(firstSuccess.validate(any(OAuth2Token.class))).willReturn(Mono.just(OAuth2TokenValidatorResult.success()));
given(secondSuccess.validate(any(OAuth2Token.class))).willReturn(Mono.just(OAuth2TokenValidatorResult.success()));
ReactiveDelegatingOAuth2TokenValidator<OAuth2Token> firstValidator =
new ReactiveDelegatingOAuth2TokenValidator<>(Arrays.asList(firstSuccess, secondSuccess));
ReactiveDelegatingOAuth2TokenValidator<OAuth2Token> secondValidator =
new ReactiveDelegatingOAuth2TokenValidator<>(firstSuccess, secondSuccess);
OAuth2Token token = mock(OAuth2Token.class);
firstValidator.validate(token).block();
secondValidator.validate(token).block();
verify(firstSuccess, times(2)).validate(token);
verify(secondSuccess, times(2)).validate(token);
}
}

57
oauth2/oauth2-core/src/test/java/org/springframework/security/oauth2/core/ReactiveWrappingOAuth2TokenValidatorTests.java

@ -0,0 +1,57 @@ @@ -0,0 +1,57 @@
/*
* Copyright 2026-present 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
*
* https://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.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.mockito.Mockito.*;
/**
* Tests for verifying {@link ReactiveWrappingOAuth2TokenValidatorTests}
*
* @author Iain Henderson
*/
public class ReactiveWrappingOAuth2TokenValidatorTests {
private static final OAuth2Error DETAIL = new OAuth2Error("error", "description", "uri");
@Test
public void validate() {
ReactiveWrappingOAuth2TokenValidator<OAuth2Token> tokenValidator =
new ReactiveWrappingOAuth2TokenValidator<>(token -> OAuth2TokenValidatorResult.success());
OAuth2Token token = mock(OAuth2Token.class);
assertThat(tokenValidator.validate(token).block().hasErrors()).isFalse();
}
@Test
public void validateFailure() {
ReactiveWrappingOAuth2TokenValidator<OAuth2Token> tokenValidator =
new ReactiveWrappingOAuth2TokenValidator<>(token -> OAuth2TokenValidatorResult.failure(DETAIL));
OAuth2Token token = mock(OAuth2Token.class);
OAuth2TokenValidatorResult result = tokenValidator.validate(token).block();
assertThat(result).isNotNull();
assertThat(result.hasErrors()).isTrue();
assertThat(result.getErrors()).containsExactly(DETAIL);
}
@Test
public void constructorWhenInvokedWithNullValidatorListThenThrowsIllegalArgumentException() {
assertThatIllegalArgumentException().isThrownBy(() -> new ReactiveWrappingOAuth2TokenValidator<>(null));
}
}

23
oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/JwtValidators.java

@ -29,6 +29,8 @@ import org.springframework.security.oauth2.core.OAuth2Error; @@ -29,6 +29,8 @@ 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.core.ReactiveDelegatingOAuth2TokenValidator;
import org.springframework.security.oauth2.core.ReactiveOAuth2TokenValidator;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
@ -37,6 +39,7 @@ import org.springframework.util.CollectionUtils; @@ -37,6 +39,7 @@ import org.springframework.util.CollectionUtils;
*
* @author Josh Cummings
* @author Rob Winch
* @author Iain Henderson
* @since 5.1
*/
public final class JwtValidators {
@ -71,8 +74,7 @@ public final class JwtValidators { @@ -71,8 +74,7 @@ public final class JwtValidators {
* result of this method to {@code DelegatingOAuth2TokenValidator} along with the
* additional validators.
* </p>
* @return - a delegating validator containing all standard validators as well as any
* supplied
* @return - a delegating validator containing all standard validators
*/
public static OAuth2TokenValidator<Jwt> createDefault() {
return new DelegatingOAuth2TokenValidator<>(Arrays.asList(JwtTypeValidator.jwt(), new JwtTimestampValidator(),
@ -80,6 +82,23 @@ public final class JwtValidators { @@ -80,6 +82,23 @@ public final class JwtValidators {
X509CertificateThumbprintValidator.DEFAULT_X509_CERTIFICATE_SUPPLIER)));
}
/**
* <p>
* Create a reactive {@link Jwt} Validator that contains all standard validators.
* </p>
* <p>
* User's wanting to leverage the defaults plus additional validation can add the
* result of this method to {@code ReactiveDelegatingOAuth2TokenValidator} along with the
* additional validators.
* </p>
* @return - a reactive delegating validator containing all standard validators
*/
public static ReactiveOAuth2TokenValidator<Jwt> createReactiveDefault() {
return new ReactiveDelegatingOAuth2TokenValidator<>(JwtTypeValidator.jwt(), new JwtTimestampValidator(),
new X509CertificateThumbprintValidator(
X509CertificateThumbprintValidator.DEFAULT_X509_CERTIFICATE_SUPPLIER));
}
/**
* <p>
* Create a {@link Jwt} default validator with standard validators and additional

223
oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/NimbusReactiveJwtDecoder.java

@ -64,7 +64,8 @@ import reactor.util.function.Tuples; @@ -64,7 +64,8 @@ import reactor.util.function.Tuples;
import org.springframework.core.convert.converter.Converter;
import org.springframework.security.oauth2.core.OAuth2Error;
import org.springframework.security.oauth2.core.OAuth2TokenValidator;
import org.springframework.security.oauth2.core.OAuth2TokenValidatorResult;
import org.springframework.security.oauth2.core.ReactiveOAuth2TokenValidator;
import org.springframework.security.oauth2.core.ReactiveWrappingOAuth2TokenValidator;
import org.springframework.security.oauth2.jose.jws.JwsAlgorithm;
import org.springframework.security.oauth2.jose.jws.MacAlgorithm;
import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
@ -97,10 +98,10 @@ public final class NimbusReactiveJwtDecoder implements ReactiveJwtDecoder { @@ -97,10 +98,10 @@ public final class NimbusReactiveJwtDecoder implements ReactiveJwtDecoder {
private final Converter<JWT, Mono<JWTClaimsSet>> jwtProcessor;
private OAuth2TokenValidator<Jwt> jwtValidator = JwtValidators.createDefault();
private ReactiveOAuth2TokenValidator<Jwt> jwtValidator = JwtValidators.createReactiveDefault();
private Converter<Map<String, Object>, Map<String, Object>> claimSetConverter = MappedJwtClaimSetConverter
.withDefaults(Collections.emptyMap());
.withDefaults(Collections.emptyMap());
/**
* Constructs a {@code NimbusReactiveJwtDecoder} using the provided parameters.
@ -129,90 +130,6 @@ public final class NimbusReactiveJwtDecoder implements ReactiveJwtDecoder { @@ -129,90 +130,6 @@ public final class NimbusReactiveJwtDecoder implements ReactiveJwtDecoder {
this.jwtProcessor = jwtProcessor;
}
/**
* Use the provided {@link OAuth2TokenValidator} to validate incoming {@link Jwt}s.
* @param jwtValidator the {@link OAuth2TokenValidator} to use
*/
public void setJwtValidator(OAuth2TokenValidator<Jwt> jwtValidator) {
Assert.notNull(jwtValidator, "jwtValidator cannot be null");
this.jwtValidator = jwtValidator;
}
/**
* Use the following {@link Converter} for manipulating the JWT's claim set
* @param claimSetConverter the {@link Converter} to use
*/
public void setClaimSetConverter(Converter<Map<String, Object>, Map<String, Object>> claimSetConverter) {
Assert.notNull(claimSetConverter, "claimSetConverter cannot be null");
this.claimSetConverter = claimSetConverter;
}
@Override
public Mono<Jwt> decode(String token) {
try {
JWT jwt = JWTParser.parse(token);
if (jwt instanceof PlainJWT) {
return Mono.error(new BadJwtException("Unsupported algorithm of " + jwt.getHeader().getAlgorithm()));
}
return this.decode(jwt);
}
catch (Exception ex) {
return Mono.error(new BadJwtException(
"An error occurred while attempting to decode the Jwt: " + ex.getMessage(), ex));
}
}
private Mono<Jwt> decode(JWT parsedToken) {
try {
// @formatter:off
return this.jwtProcessor.convert(parsedToken)
.map((set) -> createJwt(parsedToken, set))
.map(this::validateJwt)
.onErrorMap((ex) -> !(ex instanceof IllegalStateException) && !(ex instanceof JwtException),
(ex) -> new JwtException("An error occurred while attempting to decode the Jwt: ", ex));
// @formatter:on
}
catch (JwtException ex) {
throw ex;
}
catch (RuntimeException ex) {
throw new JwtException("An error occurred while attempting to decode the Jwt: " + ex.getMessage(), ex);
}
}
private Jwt createJwt(JWT parsedJwt, JWTClaimsSet jwtClaimsSet) {
try {
Map<String, Object> headers = new LinkedHashMap<>(parsedJwt.getHeader().toJSONObject());
Map<String, Object> claims = this.claimSetConverter.convert(jwtClaimsSet.getClaims());
return Jwt.withTokenValue(parsedJwt.getParsedString())
.headers((h) -> h.putAll(headers))
.claims((c) -> c.putAll(claims))
.build();
}
catch (Exception ex) {
throw new BadJwtException("An error occurred while attempting to decode the Jwt: " + ex.getMessage(), ex);
}
}
private Jwt validateJwt(Jwt jwt) {
OAuth2TokenValidatorResult result = this.jwtValidator.validate(jwt);
if (result.hasErrors()) {
Collection<OAuth2Error> errors = result.getErrors();
String validationErrorString = getJwtValidationExceptionMessage(errors);
throw new JwtValidationException(validationErrorString, errors);
}
return jwt;
}
private String getJwtValidationExceptionMessage(Collection<OAuth2Error> errors) {
for (OAuth2Error oAuth2Error : errors) {
if (StringUtils.hasLength(oAuth2Error.getDescription())) {
return oAuth2Error.getDescription();
}
}
return "Unable to validate Jwt";
}
/**
* Use the given <a href=
* "https://openid.net/specs/openid-connect-core-1_0.html#IssuerIdentifier">Issuer</a>
@ -305,6 +222,102 @@ public final class NimbusReactiveJwtDecoder implements ReactiveJwtDecoder { @@ -305,6 +222,102 @@ public final class NimbusReactiveJwtDecoder implements ReactiveJwtDecoder {
}
}
/**
* Use the provided {@link OAuth2TokenValidator} to validate incoming {@link Jwt}s.
* @param jwtValidator the {@link OAuth2TokenValidator} to use
*/
public void setJwtValidator(OAuth2TokenValidator<Jwt> jwtValidator) {
Assert.notNull(jwtValidator, "jwtValidator cannot be null");
this.jwtValidator = new ReactiveWrappingOAuth2TokenValidator<>(jwtValidator);
}
/**
* Use the provided {@link OAuth2TokenValidator} to validate incoming {@link Jwt}s.
* @param jwtValidator the {@link OAuth2TokenValidator} to use
*/
public void setJwtValidator(ReactiveOAuth2TokenValidator<Jwt> jwtValidator) {
Assert.notNull(jwtValidator, "jwtValidator cannot be null");
this.jwtValidator = jwtValidator;
}
/**
* Use the following {@link Converter} for manipulating the JWT's claim set
* @param claimSetConverter the {@link Converter} to use
*/
public void setClaimSetConverter(Converter<Map<String, Object>, Map<String, Object>> claimSetConverter) {
Assert.notNull(claimSetConverter, "claimSetConverter cannot be null");
this.claimSetConverter = claimSetConverter;
}
@Override
public Mono<Jwt> decode(String token) {
try {
JWT jwt = JWTParser.parse(token);
if (jwt instanceof PlainJWT) {
return Mono.error(new BadJwtException("Unsupported algorithm of " + jwt.getHeader().getAlgorithm()));
}
return this.decode(jwt);
}
catch (Exception ex) {
return Mono.error(new BadJwtException(
"An error occurred while attempting to decode the Jwt: " + ex.getMessage(), ex));
}
}
private Mono<Jwt> decode(JWT parsedToken) {
try {
// @formatter:off
return this.jwtProcessor.convert(parsedToken)
.map((set) -> createJwt(parsedToken, set))
.flatMap(this::validateJwt)
.onErrorMap((ex) -> !(ex instanceof IllegalStateException) && !(ex instanceof JwtException),
(ex) -> new JwtException("An error occurred while attempting to decode the Jwt: ", ex));
// @formatter:on
}
catch (JwtException ex) {
throw ex;
}
catch (RuntimeException ex) {
throw new JwtException("An error occurred while attempting to decode the Jwt: " + ex.getMessage(), ex);
}
}
private Jwt createJwt(JWT parsedJwt, JWTClaimsSet jwtClaimsSet) {
try {
Map<String, Object> headers = new LinkedHashMap<>(parsedJwt.getHeader().toJSONObject());
Map<String, Object> claims = this.claimSetConverter.convert(jwtClaimsSet.getClaims());
return Jwt.withTokenValue(parsedJwt.getParsedString())
.headers((h) -> h.putAll(headers))
.claims((c) -> c.putAll(claims))
.build();
}
catch (Exception ex) {
throw new BadJwtException("An error occurred while attempting to decode the Jwt: " + ex.getMessage(), ex);
}
}
private Mono<Jwt> validateJwt(Jwt jwt) {
return this.jwtValidator.validate(jwt).handle((result, sink) -> {
if (result.hasErrors()) {
Collection<OAuth2Error> errors = result.getErrors();
String validationErrorString = getJwtValidationExceptionMessage(errors);
sink.error(new JwtValidationException(validationErrorString, errors));
}
else {
sink.next(jwt);
}
});
}
private String getJwtValidationExceptionMessage(Collection<OAuth2Error> errors) {
for (OAuth2Error oAuth2Error : errors) {
if (StringUtils.hasLength(oAuth2Error.getDescription())) {
return oAuth2Error.getDescription();
}
}
return "Unable to validate Jwt";
}
/**
* A builder for creating {@link NimbusReactiveJwtDecoder} instances based on a
* <a target="_blank" href="https://tools.ietf.org/html/rfc7517#section-5">JWK Set</a>
@ -322,15 +335,11 @@ public final class NimbusReactiveJwtDecoder implements ReactiveJwtDecoder { @@ -322,15 +335,11 @@ public final class NimbusReactiveJwtDecoder implements ReactiveJwtDecoder {
private static final Duration FOREVER = Duration.ofMillis(Long.MAX_VALUE);
private Function<WebClient, Mono<String>> jwkSetUri;
private final Function<WebClient, Mono<String>> jwkSetUri;
private final Set<SignatureAlgorithm> signatureAlgorithms = new HashSet<>();
private Function<ReactiveRemoteJWKSource, Mono<Set<JWSAlgorithm>>> defaultAlgorithms = (source) -> Mono
.just(Set.of(JWSAlgorithm.RS256));
.just(Set.of(JWSAlgorithm.RS256));
private JOSEObjectTypeVerifier<JWKSecurityContext> typeVerifier = NO_TYPE_VERIFIER;
private Set<SignatureAlgorithm> signatureAlgorithms = new HashSet<>();
private WebClient webClient = WebClient.create();
private BiFunction<ReactiveRemoteJWKSource, ConfigurableJWTProcessor<JWKSecurityContext>, Mono<ConfigurableJWTProcessor<JWKSecurityContext>>> jwtProcessorCustomizer;
@ -478,7 +487,7 @@ public final class NimbusReactiveJwtDecoder implements ReactiveJwtDecoder { @@ -478,7 +487,7 @@ public final class NimbusReactiveJwtDecoder implements ReactiveJwtDecoder {
JWKSecurityContextJWKSet jwkSource = new JWKSecurityContextJWKSet();
if (this.signatureAlgorithms.isEmpty()) {
return this.defaultAlgorithms.apply(source)
.map((algorithms) -> new JWSVerificationKeySelector<>(algorithms, jwkSource));
.map((algorithms) -> new JWSVerificationKeySelector<>(algorithms, jwkSource));
}
Set<JWSAlgorithm> jwsAlgorithms = new HashSet<>();
for (SignatureAlgorithm signatureAlgorithm : this.signatureAlgorithms) {
@ -496,21 +505,21 @@ public final class NimbusReactiveJwtDecoder implements ReactiveJwtDecoder { @@ -496,21 +505,21 @@ public final class NimbusReactiveJwtDecoder implements ReactiveJwtDecoder {
source.setWebClient(this.webClient);
Mono<JWSKeySelector<JWKSecurityContext>> jwsKeySelector = jwsKeySelector(source);
Mono<Tuple2<ConfigurableJWTProcessor<JWKSecurityContext>, Function<JWSAlgorithm, Boolean>>> jwtProcessorMono = jwsKeySelector
.flatMap((selector) -> {
jwtProcessor.setJWSKeySelector(selector);
jwtProcessor.setJWSTypeVerifier(this.typeVerifier);
return this.jwtProcessorCustomizer.apply(source, jwtProcessor);
})
.map((processor) -> Tuples.of(processor, getExpectedJwsAlgorithms(processor.getJWSKeySelector())))
.cache((processor) -> FOREVER, (ex) -> Duration.ZERO, () -> Duration.ZERO);
.flatMap((selector) -> {
jwtProcessor.setJWSKeySelector(selector);
jwtProcessor.setJWSTypeVerifier(this.typeVerifier);
return this.jwtProcessorCustomizer.apply(source, jwtProcessor);
})
.map((processor) -> Tuples.of(processor, getExpectedJwsAlgorithms(processor.getJWSKeySelector())))
.cache((processor) -> FOREVER, (ex) -> Duration.ZERO, () -> Duration.ZERO);
return (jwt) -> {
return jwtProcessorMono.flatMap((tuple) -> {
ConfigurableJWTProcessor<JWKSecurityContext> processor = tuple.getT1();
Function<JWSAlgorithm, Boolean> expectedJwsAlgorithms = tuple.getT2();
JWKSelector selector = createSelector(expectedJwsAlgorithms, jwt.getHeader());
return source.get(selector)
.onErrorMap((ex) -> new IllegalStateException("Could not obtain the keys", ex))
.map((jwkList) -> createClaimsSet(processor, jwt, new JWKSecurityContext(jwkList)));
.onErrorMap((ex) -> new IllegalStateException("Could not obtain the keys", ex))
.map((jwkList) -> createClaimsSet(processor, jwt, new JWKSecurityContext(jwkList)));
});
};
}
@ -926,9 +935,9 @@ public final class NimbusReactiveJwtDecoder implements ReactiveJwtDecoder { @@ -926,9 +935,9 @@ public final class NimbusReactiveJwtDecoder implements ReactiveJwtDecoder {
return (jwt) -> {
if (jwt instanceof SignedJWT) {
return this.jwkSource.apply((SignedJWT) jwt)
.onErrorMap((e) -> new IllegalStateException("Could not obtain the keys", e))
.collectList()
.map((jwks) -> createClaimsSet(jwtProcessor, jwt, new JWKSecurityContext(jwks)));
.onErrorMap((e) -> new IllegalStateException("Could not obtain the keys", e))
.collectList()
.map((jwks) -> createClaimsSet(jwtProcessor, jwt, new JWKSecurityContext(jwks)));
}
throw new BadJwtException("Unsupported algorithm of " + jwt.getHeader().getAlgorithm());
};

131
oauth2/oauth2-jose/src/test/java/org/springframework/security/oauth2/jwt/NimbusReactiveJwtDecoderTests.java

@ -66,36 +66,30 @@ import org.springframework.core.convert.converter.Converter; @@ -66,36 +66,30 @@ import org.springframework.core.convert.converter.Converter;
import org.springframework.security.oauth2.core.OAuth2Error;
import org.springframework.security.oauth2.core.OAuth2TokenValidator;
import org.springframework.security.oauth2.core.OAuth2TokenValidatorResult;
import org.springframework.security.oauth2.core.ReactiveOAuth2TokenValidator;
import org.springframework.security.oauth2.jose.TestKeys;
import org.springframework.security.oauth2.jose.jws.MacAlgorithm;
import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
import org.springframework.web.reactive.function.client.WebClient;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.assertj.core.api.Assertions.*;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.BDDMockito.*;
/**
* @author Rob Winch
* @author Joe Grandja
* @author Iain Henderson
* @since 5.1
*/
public class NimbusReactiveJwtDecoderTests {
private String expired = "eyJraWQiOiJrZXktaWQtMSIsImFsZyI6IlJTMjU2In0.eyJzY29wZSI6Im1lc3NhZ2U6cmVhZCIsImV4cCI6MTUyOTkzNzYzMX0.Dt5jFOKkB8zAmjciwvlGkj4LNStXWH0HNIfr8YYajIthBIpVgY5Hg_JL8GBmUFzKDgyusT0q60OOg8_Pdi4Lu-VTWyYutLSlNUNayMlyBaVEWfyZJnh2_OwMZr1vRys6HF-o1qZldhwcfvczHg61LwPa1ISoqaAltDTzBu9cGISz2iBUCuR0x71QhbuRNyJdjsyS96NqiM_TspyiOSxmlNch2oAef1MssOQ23CrKilIvEDsz_zk5H94q7rH0giWGdEHCENESsTJS0zvzH6r2xIWjd5WnihFpCPkwznEayxaEhrdvJqT_ceyXCIfY4m3vujPQHNDG0UshpwvDuEbPUg";
private String messageReadToken = "eyJraWQiOiJrZXktaWQtMSIsImFsZyI6IlJTMjU2In0.eyJzY29wZSI6Im1lc3NhZ2U6cmVhZCIsImV4cCI6OTIyMzM3MjAwNjA5NjM3NX0.bnQ8IJDXmQbmIXWku0YT1HOyV_3d0iQSA_0W2CmPyELhsxFETzBEEcZ0v0xCBiswDT51rwD83wbX3YXxb84fM64AhpU8wWOxLjha4J6HJX2JnlG47ydaAVD7eWGSYTavyyQ-CwUjQWrfMVcObFZLYG11ydzRYOR9-aiHcK3AobcTcS8jZFeI8EGQV_Cd3IJ018uFCf6VnXLv7eV2kRt08Go2RiPLW47ExvD7Dzzz_wDBKfb4pNem7fDvuzB3UPcp5m9QvLZicnbS_6AvDi6P1y_DFJf-1T5gkGmX5piDH1L1jg2Yl6tjmXbk5B3VhsyjJuXE6gzq1d-xie0Z1NVOxw";
private String unsignedToken = "eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJleHAiOi0yMDMzMjI0OTcsImp0aSI6IjEyMyIsInR5cCI6IkpXVCJ9.";
private static KeyFactory kf;
private final String expired = "eyJraWQiOiJrZXktaWQtMSIsImFsZyI6IlJTMjU2In0.eyJzY29wZSI6Im1lc3NhZ2U6cmVhZCIsImV4cCI6MTUyOTkzNzYzMX0.Dt5jFOKkB8zAmjciwvlGkj4LNStXWH0HNIfr8YYajIthBIpVgY5Hg_JL8GBmUFzKDgyusT0q60OOg8_Pdi4Lu-VTWyYutLSlNUNayMlyBaVEWfyZJnh2_OwMZr1vRys6HF-o1qZldhwcfvczHg61LwPa1ISoqaAltDTzBu9cGISz2iBUCuR0x71QhbuRNyJdjsyS96NqiM_TspyiOSxmlNch2oAef1MssOQ23CrKilIvEDsz_zk5H94q7rH0giWGdEHCENESsTJS0zvzH6r2xIWjd5WnihFpCPkwznEayxaEhrdvJqT_ceyXCIfY4m3vujPQHNDG0UshpwvDuEbPUg";
private final String messageReadToken = "eyJraWQiOiJrZXktaWQtMSIsImFsZyI6IlJTMjU2In0.eyJzY29wZSI6Im1lc3NhZ2U6cmVhZCIsImV4cCI6OTIyMzM3MjAwNjA5NjM3NX0.bnQ8IJDXmQbmIXWku0YT1HOyV_3d0iQSA_0W2CmPyELhsxFETzBEEcZ0v0xCBiswDT51rwD83wbX3YXxb84fM64AhpU8wWOxLjha4J6HJX2JnlG47ydaAVD7eWGSYTavyyQ-CwUjQWrfMVcObFZLYG11ydzRYOR9-aiHcK3AobcTcS8jZFeI8EGQV_Cd3IJ018uFCf6VnXLv7eV2kRt08Go2RiPLW47ExvD7Dzzz_wDBKfb4pNem7fDvuzB3UPcp5m9QvLZicnbS_6AvDi6P1y_DFJf-1T5gkGmX5piDH1L1jg2Yl6tjmXbk5B3VhsyjJuXE6gzq1d-xie0Z1NVOxw";
private final String unsignedToken = "eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJleHAiOi0yMDMzMjI0OTcsImp0aSI6IjEyMyIsInR5cCI6IkpXVCJ9.";
// @formatter:on
// @formatter:off
private String jwkSet = "{\n"
private final String jwkSet = "{\n"
+ " \"keys\":[\n"
+ " {\n"
+ " \"kty\":\"RSA\",\n"
@ -106,27 +100,29 @@ public class NimbusReactiveJwtDecoderTests { @@ -106,27 +100,29 @@ public class NimbusReactiveJwtDecoderTests {
+ " }\n"
+ " ]\n"
+ "}";
// @formatter:on
private String jwkSetUri = "https://issuer/certs";
private String rsa512 = "eyJhbGciOiJSUzUxMiJ9.eyJzdWIiOiJ0ZXN0LXN1YmplY3QiLCJleHAiOjE5NzQzMjYxMTl9.LKAx-60EBfD7jC1jb1eKcjO4uLvf3ssISV-8tN-qp7gAjSvKvj4YA9-V2mIb6jcS1X_xGmNy6EIimZXpWaBR3nJmeu-jpe85u4WaW2Ztr8ecAi-dTO7ZozwdtljKuBKKvj4u1nF70zyCNl15AozSG0W1ASrjUuWrJtfyDG6WoZ8VfNMuhtU-xUYUFvscmeZKUYQcJ1KS-oV5tHeF8aNiwQoiPC_9KXCOZtNEJFdq6-uzFdHxvOP2yex5Gbmg5hXonauIFXG2ZPPGdXzm-5xkhBpgM8U7A_6wb3So8wBvLYYm2245QUump63AJRAy8tQpwt4n9MvQxQgS3z9R-NK92A";
private String rsa256 = "eyJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJ0ZXN0LXN1YmplY3QiLCJleHAiOjE5NzQzMjYzMzl9.CT-H2OWEqmSs1NWmnta5ealLFvM8OlbQTjGhfRcKLNxrTrzsOkqBJl-AN3k16BQU7mS32o744TiiZ29NcDlxPsr1MqTlN86-dobPiuNIDLp3A1bOVdXMcVFuMYkrNv0yW0tGS9OjEqsCCuZDkZ1by6AhsHLbGwRY-6AQdcRouZygGpOQu1hNun5j8q5DpSTY4AXKARIFlF-O3OpVbPJ0ebr3Ki-i3U9p_55H0e4-wx2bqcApWlqgofl1I8NKWacbhZgn81iibup2W7E0CzCzh71u1Mcy3xk1sYePx-dwcxJnHmxJReBBWjJZEAeCrkbnn_OCuo2fA-EQyNJtlN5F2w";
private String publicKey = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAq4yKxb6SNePdDmQi9xFCrP6QvHosErQzryknQTTTffs0t3cy3Er3lIceuhZ7yQNSCDfPFqG8GoyoKhuChRiA5D+J2ab7bqTa1QJKfnCyERoscftgN2fXPHjHoiKbpGV2tMVw8mXl//tePOAiKbMJaBUnlAvJgkk1rVm08dSwpLC1sr2M19euf9jwnRGkMRZuhp9iCPgECRke5T8Ixpv0uQjSmGHnWUKTFlbj8sM83suROR1Ue64JSGScANc5vk3huJ/J97qTC+K2oKj6L8d9O8dpc4obijEOJwpydNvTYDgbiivYeSB00KS9jlBkQ5B2QqLvLVEygDl3dp59nGx6YQIDAQAB";
private final String jwkSetUri = "https://issuer/certs";
private final String rsa512 = "eyJhbGciOiJSUzUxMiJ9.eyJzdWIiOiJ0ZXN0LXN1YmplY3QiLCJleHAiOjE5NzQzMjYxMTl9.LKAx-60EBfD7jC1jb1eKcjO4uLvf3ssISV-8tN-qp7gAjSvKvj4YA9-V2mIb6jcS1X_xGmNy6EIimZXpWaBR3nJmeu-jpe85u4WaW2Ztr8ecAi-dTO7ZozwdtljKuBKKvj4u1nF70zyCNl15AozSG0W1ASrjUuWrJtfyDG6WoZ8VfNMuhtU-xUYUFvscmeZKUYQcJ1KS-oV5tHeF8aNiwQoiPC_9KXCOZtNEJFdq6-uzFdHxvOP2yex5Gbmg5hXonauIFXG2ZPPGdXzm-5xkhBpgM8U7A_6wb3So8wBvLYYm2245QUump63AJRAy8tQpwt4n9MvQxQgS3z9R-NK92A";
private final String rsa256 = "eyJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJ0ZXN0LXN1YmplY3QiLCJleHAiOjE5NzQzMjYzMzl9.CT-H2OWEqmSs1NWmnta5ealLFvM8OlbQTjGhfRcKLNxrTrzsOkqBJl-AN3k16BQU7mS32o744TiiZ29NcDlxPsr1MqTlN86-dobPiuNIDLp3A1bOVdXMcVFuMYkrNv0yW0tGS9OjEqsCCuZDkZ1by6AhsHLbGwRY-6AQdcRouZygGpOQu1hNun5j8q5DpSTY4AXKARIFlF-O3OpVbPJ0ebr3Ki-i3U9p_55H0e4-wx2bqcApWlqgofl1I8NKWacbhZgn81iibup2W7E0CzCzh71u1Mcy3xk1sYePx-dwcxJnHmxJReBBWjJZEAeCrkbnn_OCuo2fA-EQyNJtlN5F2w";
private final String publicKey = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAq4yKxb6SNePdDmQi9xFCrP6QvHosErQzryknQTTTffs0t3cy3Er3lIceuhZ7yQNSCDfPFqG8GoyoKhuChRiA5D+J2ab7bqTa1QJKfnCyERoscftgN2fXPHjHoiKbpGV2tMVw8mXl//tePOAiKbMJaBUnlAvJgkk1rVm08dSwpLC1sr2M19euf9jwnRGkMRZuhp9iCPgECRke5T8Ixpv0uQjSmGHnWUKTFlbj8sM83suROR1Ue64JSGScANc5vk3huJ/J97qTC+K2oKj6L8d9O8dpc4obijEOJwpydNvTYDgbiivYeSB00KS9jlBkQ5B2QqLvLVEygDl3dp59nGx6YQIDAQAB";
private MockWebServer server;
private NimbusReactiveJwtDecoder decoder;
private static KeyFactory kf;
@BeforeAll
public static void keyFactory() throws NoSuchAlgorithmException {
kf = KeyFactory.getInstance("RSA");
}
private static WebClient mockJwkSetResponse(String response) {
WebClient real = WebClient.builder().build();
WebClient.RequestHeadersUriSpec spec = spy(real.get());
WebClient webClient = spy(WebClient.class);
given(webClient.get()).willReturn(spec);
WebClient.ResponseSpec responseSpec = mock(WebClient.ResponseSpec.class);
given(responseSpec.bodyToMono(String.class)).willReturn(Mono.just(response));
given(spec.retrieve()).willReturn(responseSpec);
return webClient;
}
@BeforeEach
public void setup() throws Exception {
this.server = new MockWebServer();
@ -159,9 +155,9 @@ public class NimbusReactiveJwtDecoderTests { @@ -159,9 +155,9 @@ public class NimbusReactiveJwtDecoderTests {
@Test
public void decodeWhenRSAPublicKeyThenSuccess() throws Exception {
byte[] bytes = Base64.getDecoder()
.decode("MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqL48v1clgFw+Evm145pmh8nRYiNt72Gupsshn7Qs8dxEydCRp1DPOV/PahPk1y2nvldBNIhfNL13JOAiJ6BTiF+2ICuICAhDArLMnTH61oL1Hepq8W1xpa9gxsnL1P51thvfmiiT4RTW57koy4xIWmIp8ZXXfYgdH2uHJ9R0CQBuYKe7nEOObjxCFWC8S30huOfW2cYtv0iB23h6w5z2fDLjddX6v/FXM7ktcokgpm3/XmvT/+bL6/GGwz9k6kJOyMTubecr+WT//le8ikY66zlplYXRQh6roFfFCL21Pt8xN5zrk+0AMZUnmi8F2S2ztSBmAVJ7H71ELXsURBVZpwIDAQAB");
.decode("MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqL48v1clgFw+Evm145pmh8nRYiNt72Gupsshn7Qs8dxEydCRp1DPOV/PahPk1y2nvldBNIhfNL13JOAiJ6BTiF+2ICuICAhDArLMnTH61oL1Hepq8W1xpa9gxsnL1P51thvfmiiT4RTW57koy4xIWmIp8ZXXfYgdH2uHJ9R0CQBuYKe7nEOObjxCFWC8S30huOfW2cYtv0iB23h6w5z2fDLjddX6v/FXM7ktcokgpm3/XmvT/+bL6/GGwz9k6kJOyMTubecr+WT//le8ikY66zlplYXRQh6roFfFCL21Pt8xN5zrk+0AMZUnmi8F2S2ztSBmAVJ7H71ELXsURBVZpwIDAQAB");
RSAPublicKey publicKey = (RSAPublicKey) KeyFactory.getInstance("RSA")
.generatePublic(new X509EncodedKeySpec(bytes));
.generatePublic(new X509EncodedKeySpec(bytes));
this.decoder = new NimbusReactiveJwtDecoder(publicKey);
String noKeyId = "eyJhbGciOiJSUzI1NiJ9.eyJzY29wZSI6IiIsImV4cCI6OTIyMzM3MjAwNjA5NjM3NX0.hNVuHSUkxdLZrDfqdmKcOi0ggmNaDuB4ZPxPtJl1gwBiXzIGN6Hwl24O2BfBZiHFKUTQDs4_RvzD71mEG3DvUrcKmdYWqIB1l8KNmxQLUDG-cAPIpJmRJgCh50tf8OhOE_Cb9E1HcsOUb47kT9iz-VayNBcmo6BmyZLdEGhsdGBrc3Mkz2dd_0PF38I2Hf_cuSjn9gBjFGtiPEXJvob3PEjVTSx_zvodT8D9p3An1R3YBZf5JSd1cQisrXgDX2k1Jmf7UKKWzgfyCgnEtRWWbsUdPqo3rSEY9GDC1iSQXsFTTC1FT_JJDkwzGf011fsU5O_Ko28TARibmKTCxAKNRQ";
this.decoder.decode(noKeyId).block();
@ -177,7 +173,7 @@ public class NimbusReactiveJwtDecoderTests { @@ -177,7 +173,7 @@ public class NimbusReactiveJwtDecoderTests {
@Test
public void decodeWhenExpiredThenFail() {
assertThatExceptionOfType(JwtValidationException.class)
.isThrownBy(() -> this.decoder.decode(this.expired).block());
.isThrownBy(() -> this.decoder.decode(this.expired).block());
}
@Test
@ -201,7 +197,7 @@ public class NimbusReactiveJwtDecoderTests { @@ -201,7 +197,7 @@ public class NimbusReactiveJwtDecoderTests {
public void decodeWhenInvalidSignatureThenFail() {
assertThatExceptionOfType(BadJwtException.class).isThrownBy(
() -> this.decoder.decode(this.messageReadToken.substring(0, this.messageReadToken.length() - 2))
.block());
.block());
}
@Test
@ -293,7 +289,7 @@ public class NimbusReactiveJwtDecoderTests { @@ -293,7 +289,7 @@ public class NimbusReactiveJwtDecoderTests {
public void setJwtValidatorWhenGivenNullThrowsIllegalArgumentException() {
// @formatter:off
assertThatIllegalArgumentException()
.isThrownBy(() -> this.decoder.setJwtValidator(null));
.isThrownBy(() -> this.decoder.setJwtValidator((ReactiveOAuth2TokenValidator<Jwt>) null));
// @formatter:on
}
@ -313,7 +309,7 @@ public class NimbusReactiveJwtDecoderTests { @@ -313,7 +309,7 @@ public class NimbusReactiveJwtDecoderTests {
@Test
public void jwsAlgorithmWhenNullThenThrowsException() {
NimbusReactiveJwtDecoder.JwkSetUriReactiveJwtDecoderBuilder builder = NimbusReactiveJwtDecoder
.withJwkSetUri(this.jwkSetUri);
.withJwkSetUri(this.jwkSetUri);
assertThatIllegalArgumentException().isThrownBy(() -> builder.jwsAlgorithm(null));
}
@ -333,7 +329,7 @@ public class NimbusReactiveJwtDecoderTests { @@ -333,7 +329,7 @@ public class NimbusReactiveJwtDecoderTests {
@Test
public void restOperationsWhenNullThenThrowsException() {
NimbusReactiveJwtDecoder.JwkSetUriReactiveJwtDecoderBuilder builder = NimbusReactiveJwtDecoder
.withJwkSetUri(this.jwkSetUri);
.withJwkSetUri(this.jwkSetUri);
// @formatter:off
assertThatIllegalArgumentException()
.isThrownBy(() -> builder.webClient(null));
@ -480,10 +476,10 @@ public class NimbusReactiveJwtDecoderTests { @@ -480,10 +476,10 @@ public class NimbusReactiveJwtDecoderTests {
@Test
public void withJwkSourceWhenJwtProcessorCustomizerNullThenThrowsIllegalArgumentException() {
assertThatIllegalArgumentException()
.isThrownBy(() -> NimbusReactiveJwtDecoder.withJwkSource((jwt) -> Flux.empty())
.jwtProcessorCustomizer(null)
.build())
.withMessage("jwtProcessorCustomizer cannot be null");
.isThrownBy(() -> NimbusReactiveJwtDecoder.withJwkSource((jwt) -> Flux.empty())
.jwtProcessorCustomizer(null)
.build())
.withMessage("jwtProcessorCustomizer cannot be null");
}
@Test
@ -593,8 +589,8 @@ public class NimbusReactiveJwtDecoderTests { @@ -593,8 +589,8 @@ public class NimbusReactiveJwtDecoderTests {
SecretKey secretKey = TestKeys.DEFAULT_SECRET_KEY;
MacAlgorithm macAlgorithm = MacAlgorithm.HS256;
JWTClaimsSet claimsSet = new JWTClaimsSet.Builder().subject("test-subject")
.expirationTime(Date.from(Instant.now().plusSeconds(60)))
.build();
.expirationTime(Date.from(Instant.now().plusSeconds(60)))
.build();
SignedJWT signedJWT = signedJwt(secretKey, macAlgorithm, claimsSet);
// @formatter:off
this.decoder = NimbusReactiveJwtDecoder.withSecretKey(secretKey)
@ -615,11 +611,11 @@ public class NimbusReactiveJwtDecoderTests { @@ -615,11 +611,11 @@ public class NimbusReactiveJwtDecoderTests {
WebClient.ResponseSpec responseSpec = mock(WebClient.ResponseSpec.class);
given(responseSpec.bodyToMono(String.class)).willReturn(Mono.just(this.jwkSet));
given(responseSpec.bodyToMono(any(ParameterizedTypeReference.class)))
.willReturn(Mono.just(Map.of("issuer", issuer, "jwks_uri", issuer + "/jwks")));
.willReturn(Mono.just(Map.of("issuer", issuer, "jwks_uri", issuer + "/jwks")));
given(spec.retrieve()).willReturn(responseSpec);
ReactiveJwtDecoder jwtDecoder = NimbusReactiveJwtDecoder.withIssuerLocation(issuer)
.webClient(webClient)
.build();
.webClient(webClient)
.build();
Jwt jwt = jwtDecoder.decode(this.messageReadToken).block();
assertThat(jwt.hasClaim(JwtClaimNames.EXP)).isNotNull();
}
@ -628,8 +624,8 @@ public class NimbusReactiveJwtDecoderTests { @@ -628,8 +624,8 @@ public class NimbusReactiveJwtDecoderTests {
public void jwsKeySelectorWhenNoAlgorithmThenReturnsRS256Selector() {
ReactiveRemoteJWKSource jwkSource = mock(ReactiveRemoteJWKSource.class);
JWSKeySelector<JWKSecurityContext> jwsKeySelector = NimbusReactiveJwtDecoder.withJwkSetUri(this.jwkSetUri)
.jwsKeySelector(jwkSource)
.block();
.jwsKeySelector(jwkSource)
.block();
assertThat(jwsKeySelector).isInstanceOf(JWSVerificationKeySelector.class);
JWSVerificationKeySelector<JWKSecurityContext> jwsVerificationKeySelector = (JWSVerificationKeySelector<JWKSecurityContext>) jwsKeySelector;
assertThat(jwsVerificationKeySelector.isAllowed(JWSAlgorithm.RS256)).isTrue();
@ -639,9 +635,9 @@ public class NimbusReactiveJwtDecoderTests { @@ -639,9 +635,9 @@ public class NimbusReactiveJwtDecoderTests {
public void jwsKeySelectorWhenOneAlgorithmThenReturnsSingleSelector() {
ReactiveRemoteJWKSource jwkSource = mock(ReactiveRemoteJWKSource.class);
JWSKeySelector<JWKSecurityContext> jwsKeySelector = NimbusReactiveJwtDecoder.withJwkSetUri(this.jwkSetUri)
.jwsAlgorithm(SignatureAlgorithm.RS512)
.jwsKeySelector(jwkSource)
.block();
.jwsAlgorithm(SignatureAlgorithm.RS512)
.jwsKeySelector(jwkSource)
.block();
assertThat(jwsKeySelector).isInstanceOf(JWSVerificationKeySelector.class);
JWSVerificationKeySelector<JWKSecurityContext> jwsVerificationKeySelector = (JWSVerificationKeySelector<JWKSecurityContext>) jwsKeySelector;
assertThat(jwsVerificationKeySelector.isAllowed(JWSAlgorithm.RS512)).isTrue();
@ -665,9 +661,9 @@ public class NimbusReactiveJwtDecoderTests { @@ -665,9 +661,9 @@ public class NimbusReactiveJwtDecoderTests {
@Test
public void decodeWhenPublicKeyValidateTypeFalseThenSkipsNimbusTypeValidation() throws Exception {
NimbusReactiveJwtDecoder jwtDecoder = NimbusReactiveJwtDecoder.withPublicKey(TestKeys.DEFAULT_PUBLIC_KEY)
.validateType(false)
.build();
jwtDecoder.setJwtValidator((jwt) -> OAuth2TokenValidatorResult.success());
.validateType(false)
.build();
jwtDecoder.setJwtValidator((OAuth2TokenValidator<Jwt>) (jwt) -> OAuth2TokenValidatorResult.success());
RSAPrivateKey privateKey = TestKeys.DEFAULT_PRIVATE_KEY;
SignedJWT jwt = signedJwt(privateKey,
new JWSHeader.Builder(JWSAlgorithm.RS256).type(JOSEObjectType.JOSE).build(),
@ -678,9 +674,9 @@ public class NimbusReactiveJwtDecoderTests { @@ -678,9 +674,9 @@ public class NimbusReactiveJwtDecoderTests {
@Test
public void decodeWhenSecretKeyValidateTypeFalseThenSkipsNimbusTypeValidation() throws Exception {
NimbusReactiveJwtDecoder jwtDecoder = NimbusReactiveJwtDecoder.withSecretKey(TestKeys.DEFAULT_SECRET_KEY)
.validateType(false)
.build();
jwtDecoder.setJwtValidator((jwt) -> OAuth2TokenValidatorResult.success());
.validateType(false)
.build();
jwtDecoder.setJwtValidator((OAuth2TokenValidator<Jwt>) (jwt) -> OAuth2TokenValidatorResult.success());
SignedJWT jwt = signedJwt(TestKeys.DEFAULT_SECRET_KEY,
new JWSHeader.Builder(JWSAlgorithm.HS256).type(JOSEObjectType.JOSE).build(),
new JWTClaimsSet.Builder().subject("subject").build());
@ -690,12 +686,12 @@ public class NimbusReactiveJwtDecoderTests { @@ -690,12 +686,12 @@ public class NimbusReactiveJwtDecoderTests {
@Test
public void decodeWhenJwkSourceValidateTypeFalseThenSkipsNimbusTypeValidation() throws Exception {
JWK jwk = new RSAKey.Builder(TestKeys.DEFAULT_PUBLIC_KEY).privateKey(TestKeys.DEFAULT_PRIVATE_KEY)
.algorithm(JWSAlgorithm.RS256)
.build();
.algorithm(JWSAlgorithm.RS256)
.build();
NimbusReactiveJwtDecoder jwtDecoder = NimbusReactiveJwtDecoder.withJwkSource((jwt) -> Flux.just(jwk))
.validateType(false)
.build();
jwtDecoder.setJwtValidator((jwt) -> OAuth2TokenValidatorResult.success());
.validateType(false)
.build();
jwtDecoder.setJwtValidator((OAuth2TokenValidator<Jwt>) (jwt) -> OAuth2TokenValidatorResult.success());
SignedJWT jwt = signedJwt(TestKeys.DEFAULT_PRIVATE_KEY,
new JWSHeader.Builder(JWSAlgorithm.RS256).type(JOSEObjectType.JOSE).build(),
new JWTClaimsSet.Builder().subject("subject").build());
@ -738,15 +734,4 @@ public class NimbusReactiveJwtDecoderTests { @@ -738,15 +734,4 @@ public class NimbusReactiveJwtDecoderTests {
return (RSAPublicKey) kf.generatePublic(spec);
}
private static WebClient mockJwkSetResponse(String response) {
WebClient real = WebClient.builder().build();
WebClient.RequestHeadersUriSpec spec = spy(real.get());
WebClient webClient = spy(WebClient.class);
given(webClient.get()).willReturn(spec);
WebClient.ResponseSpec responseSpec = mock(WebClient.ResponseSpec.class);
given(responseSpec.bodyToMono(String.class)).willReturn(Mono.just(response));
given(spec.retrieve()).willReturn(responseSpec);
return webClient;
}
}

Loading…
Cancel
Save