Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@
*
* @author Rob Winch
* @author Joe Grandja
* @author Kai Zander
* @since 5.1
* @see ReactiveJwtDecoder
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc7519">JSON Web Token
Expand All @@ -99,8 +100,8 @@ public final class NimbusReactiveJwtDecoder implements ReactiveJwtDecoder {

private OAuth2TokenValidator<Jwt> jwtValidator = JwtValidators.createDefault();

private Converter<Map<String, Object>, Map<String, Object>> claimSetConverter = MappedJwtClaimSetConverter
.withDefaults(Collections.emptyMap());
private Converter<Map<String, Object>, Mono<Map<String, Object>>> claimSetConverter = new ReactiveClaimSetConverterAdapter(
MappedJwtClaimSetConverter.withDefaults(Collections.emptyMap()));

/**
* Constructs a {@code NimbusReactiveJwtDecoder} using the provided parameters.
Expand Down Expand Up @@ -139,11 +140,38 @@ public void setJwtValidator(OAuth2TokenValidator<Jwt> jwtValidator) {
}

/**
* Use the following {@link Converter} for manipulating the JWT's claim set
* Use the following {@link Converter} for synchronously manipulating the JWT's claim
* set.
* <p>
* Use {@linkplain #setReactiveClaimSetConverter(Converter)} for asynchronously
* manipulating the JWT's claim set.
* <p>
* <b>Note:</b> Configure <i>either</i> a synchronous <i>or</i> an asynchronous
* converter.
* @param claimSetConverter the {@link Converter} to use
* @see #setReactiveClaimSetConverter(Converter)
*/
public void setClaimSetConverter(Converter<Map<String, Object>, Map<String, Object>> claimSetConverter) {
Assert.notNull(claimSetConverter, "claimSetConverter cannot be null");
this.claimSetConverter = new ReactiveClaimSetConverterAdapter(claimSetConverter);
}

/**
* Use the following {@link Converter} for manipulating the JWT's claim set in a
* reactive way.
* <p>
* Use {@linkplain #setClaimSetConverter(Converter)} for synchronously manipulating
* the JWT's claim set.
* <p>
* <b>Note:</b> Configure <i>either</i> a synchronous <i>or</i> an asynchronous
* converter.
* @param claimSetConverter the {@link Converter} to use
* @since 7.1
* @see #setClaimSetConverter(Converter)
*/
public void setReactiveClaimSetConverter(
Converter<Map<String, Object>, Mono<Map<String, Object>>> claimSetConverter) {
Assert.notNull(claimSetConverter, "claimSetConverter cannot be null");
this.claimSetConverter = claimSetConverter;
}

Expand All @@ -166,7 +194,7 @@ private Mono<Jwt> decode(JWT parsedToken) {
try {
// @formatter:off
return this.jwtProcessor.convert(parsedToken)
.map((set) -> createJwt(parsedToken, set))
.flatMap((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));
Expand All @@ -180,18 +208,16 @@ private Mono<Jwt> decode(JWT parsedToken) {
}
}

private Jwt createJwt(JWT parsedJwt, JWTClaimsSet jwtClaimsSet) {
try {
private Mono<Jwt> createJwt(JWT parsedJwt, JWTClaimsSet jwtClaimsSet) {
return this.claimSetConverter.convert(jwtClaimsSet.getClaims()).map((claims) -> {
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);
}
})
.onErrorMap((ex) -> new BadJwtException(
"An error occurred while attempting to decode the Jwt: " + ex.getMessage(), ex));
}

private Jwt validateJwt(Jwt jwt) {
Expand Down Expand Up @@ -946,4 +972,15 @@ Converter<JWT, Mono<JWTClaimsSet>> processor() {

}

private record ReactiveClaimSetConverterAdapter(Converter<Map<String, Object>, Map<String, Object>> delegate)
implements
Converter<Map<String, Object>, Mono<Map<String, Object>>> {

@Override
public Mono<Map<String, Object>> convert(Map<String, Object> claims) {
return Mono.fromSupplier(() -> this.delegate.convert(claims));
}

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@
/**
* @author Rob Winch
* @author Joe Grandja
* @author Kai Zander
* @since 5.1
*/
public class NimbusReactiveJwtDecoderTests {
Expand Down Expand Up @@ -267,7 +268,7 @@ public void decodeWhenReadingErrorPickTheFirstErrorMessage() {
}

@Test
public void decodeWhenUsingSignedJwtThenReturnsClaimsGivenByClaimSetConverter() {
public void decodeWhenUsingSignedJwtThenReturnsClaimsGivenBySynchronousClaimSetConverter() {
Converter<Map<String, Object>, Map<String, Object>> claimSetConverter = mock(Converter.class);
this.decoder.setClaimSetConverter(claimSetConverter);
given(claimSetConverter.convert(any(Map.class))).willReturn(Collections.singletonMap("custom", "value"));
Expand All @@ -277,6 +278,18 @@ public void decodeWhenUsingSignedJwtThenReturnsClaimsGivenByClaimSetConverter()
verify(claimSetConverter).convert(any(Map.class));
}

@Test
public void decodeWhenUsingSignedJwtThenReturnsClaimsGivenByAsynchronousClaimSetConverter() {
Converter<Map<String, Object>, Mono<Map<String, Object>>> claimSetConverter = mock(Converter.class);
this.decoder.setReactiveClaimSetConverter(claimSetConverter);
given(claimSetConverter.convert(any(Map.class)))
.willReturn(Mono.just(Collections.singletonMap("custom", "value")));
Jwt jwt = this.decoder.decode(this.messageReadToken).block();
assertThat(jwt.getClaims()).hasSize(1);
assertThat(jwt.getClaims()).containsEntry("custom", "value");
verify(claimSetConverter).convert(any(Map.class));
}

// gh-7885
@Test
public void decodeWhenClaimSetConverterFailsThenBadJwtException() {
Expand Down
Loading