/** * Configures a {@link ReactiveJwtDecoder} using * <a target="_blank" href="https://tools.ietf.org/html/rfc7517">JSON Web Key (JWK)</a> URL * @param jwkSetUri the URL to use. * @return the {@code JwtSpec} for additional configuration */ public JwtSpec jwkSetUri(String jwkSetUri) { this.jwtDecoder = new NimbusReactiveJwtDecoder(jwkSetUri); return this; }
/** * Creates a {@link ReactiveJwtDecoder} using the provided * <a href="http://openid.net/specs/openid-connect-core-1_0.html#IssuerIdentifier">Issuer</a> by making an * <a href="https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfigurationRequest">OpenID Provider * Configuration Request</a> and using the values in the * <a href="https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfigurationResponse">OpenID * Provider Configuration Response</a> to initialize the {@link ReactiveJwtDecoder}. * * @param oidcIssuerLocation the <a href="http://openid.net/specs/openid-connect-core-1_0.html#IssuerIdentifier">Issuer</a> * @return a {@link ReactiveJwtDecoder} that was initialized by the OpenID Provider Configuration. */ public static ReactiveJwtDecoder fromOidcIssuerLocation(String oidcIssuerLocation) { Map<String, Object> openidConfiguration = getOpenidConfiguration(oidcIssuerLocation); String metadataIssuer = "(unavailable)"; if (openidConfiguration.containsKey("issuer")) { metadataIssuer = openidConfiguration.get("issuer").toString(); } if (!oidcIssuerLocation.equals(metadataIssuer)) { throw new IllegalStateException("The Issuer \"" + metadataIssuer + "\" provided in the OpenID Configuration " + "did not match the requested issuer \"" + oidcIssuerLocation + "\""); } OAuth2TokenValidator<Jwt> jwtValidator = JwtValidators.createDefaultWithIssuer(oidcIssuerLocation); NimbusReactiveJwtDecoder jwtDecoder = new NimbusReactiveJwtDecoder(openidConfiguration.get("jwks_uri").toString()); jwtDecoder.setJwtValidator(jwtValidator); return jwtDecoder; }
@Test public void decodeWhenInvalidJwkSetUrlThenFail() { this.decoder = new NimbusReactiveJwtDecoder("http://localhost:1280/certs"); assertThatCode(() -> this.decoder.decode(this.messageReadToken).block()) .isInstanceOf(IllegalStateException.class); }
private Mono<Jwt> decode(SignedJWT parsedToken) { try { JWKSelector selector = this.jwkSelectorFactory .createSelector(parsedToken.getHeader()); return this.reactiveJwkSource.get(selector) .onErrorMap(e -> new IllegalStateException("Could not obtain the keys", e)) .map(jwkList -> createClaimsSet(parsedToken, jwkList)) .map(set -> createJwt(parsedToken, set)) .map(this::validateJwt) .onErrorMap(e -> !(e instanceof IllegalStateException) && !(e instanceof JwtException), e -> new JwtException("An error occurred while attempting to decode the Jwt: ", e)); } catch (RuntimeException ex) { throw new JwtException("An error occurred while attempting to decode the Jwt: " + ex.getMessage(), ex); } }
@Test public void decodeWhenUsingCustomValidatorThenValidatorIsInvoked() { OAuth2TokenValidator jwtValidator = mock(OAuth2TokenValidator.class); this.decoder.setJwtValidator(jwtValidator); OAuth2Error error = new OAuth2Error("mock-error", "mock-description", "mock-uri"); OAuth2TokenValidatorResult result = OAuth2TokenValidatorResult.failure(error); when(jwtValidator.validate(any(Jwt.class))).thenReturn(result); assertThatCode(() -> this.decoder.decode(this.messageReadToken).block()) .isInstanceOf(JwtException.class) .hasMessageContaining("mock-description"); }
@Test public void decodeWhenIssuedAtThenSuccess() { String withIssuedAt = "eyJraWQiOiJrZXktaWQtMSIsImFsZyI6IlJTMjU2In0.eyJzY29wZSI6IiIsImV4cCI6OTIyMzM3MjAwNjA5NjM3NSwiaWF0IjoxNTI5OTQyNDQ4fQ.LBzAJO-FR-uJDHST61oX4kimuQjz6QMJPW_mvEXRB6A-fMQWpfTQ089eboipAqsb33XnwWth9ELju9HMWLk0FjlWVVzwObh9FcoKelmPNR8mZIlFG-pAYGgSwi8HufyLabXHntFavBiFtqwp_z9clSOFK1RxWvt3lywEbGgtCKve0BXOjfKWiH1qe4QKGixH-NFxidvz8Qd5WbJwyb9tChC6ZKoKPv7Jp-N5KpxkY-O2iUtINvn4xOSactUsvKHgF8ZzZjvJGzG57r606OZXaNtoElQzjAPU5xDGg5liuEJzfBhvqiWCLRmSuZ33qwp3aoBnFgEw0B85gsNe3ggABg"; Jwt jwt = this.decoder.decode(withIssuedAt).block(); assertThat(jwt.getClaims().get(JwtClaimNames.IAT)).isEqualTo(Instant.ofEpochSecond(1529942448L)); }
@Test public void decodeWhenUsingSignedJwtThenReturnsClaimsGivenByClaimSetConverter() { Converter<Map<String, Object>, Map<String, Object>> claimSetConverter = mock(Converter.class); this.decoder.setClaimSetConverter(claimSetConverter); when(claimSetConverter.convert(any(Map.class))).thenReturn(Collections.singletonMap("custom", "value")); Jwt jwt = this.decoder.decode(this.messageReadToken).block(); assertThat(jwt.getClaims().size()).isEqualTo(1); assertThat(jwt.getClaims().get("custom")).isEqualTo("value"); verify(claimSetConverter).convert(any(Map.class)); }
@Test public void setJwtValidatorWhenGivenNullThrowsIllegalArgumentException() { assertThatCode(() -> this.decoder.setJwtValidator(null)) .isInstanceOf(IllegalArgumentException.class); }
public NimbusReactiveJwtDecoder(RSAPublicKey publicKey) { JWSAlgorithm algorithm = JWSAlgorithm.parse(JwsAlgorithms.RS256); RSAKey rsaKey = rsaKey(publicKey); JWKSet jwkSet = new JWKSet(rsaKey); JWKSource jwkSource = new ImmutableJWKSet<>(jwkSet); JWSKeySelector<JWKContext> jwsKeySelector = new JWSVerificationKeySelector<>(algorithm, jwkSource); DefaultJWTProcessor jwtProcessor = new DefaultJWTProcessor<>(); jwtProcessor.setJWSKeySelector(jwsKeySelector); jwtProcessor.setJWTClaimsSetVerifier((claims, context) -> {}); this.jwtProcessor = jwtProcessor; this.reactiveJwkSource = new ReactiveJWKSourceAdapter(jwkSource); this.jwkSelectorFactory = new JWKSelectorFactory(algorithm); }
@Test public void decodeWhenUnsignedTokenThenMessageDoesNotMentionClass() { assertThatCode(() -> this.decoder.decode(this.unsignedToken).block()) .isInstanceOf(JwtException.class) .hasMessage("Unsupported algorithm of none"); }
private Mono<Jwt> decode(SignedJWT parsedToken) { try { JWKSelector selector = this.jwkSelectorFactory .createSelector(parsedToken.getHeader()); return this.reactiveJwkSource.get(selector) .onErrorMap(e -> new IllegalStateException("Could not obtain the keys", e)) .map(jwkList -> createClaimsSet(parsedToken, jwkList)) .map(set -> createJwt(parsedToken, set)) .map(this::validateJwt) .onErrorMap(e -> !(e instanceof IllegalStateException) && !(e instanceof JwtException), e -> new JwtException("An error occurred while attempting to decode the Jwt: ", e)); } catch (RuntimeException ex) { throw new JwtException("An error occurred while attempting to decode the Jwt: " + ex.getMessage(), ex); } }
public NimbusReactiveJwtDecoder(RSAPublicKey publicKey) { JWSAlgorithm algorithm = JWSAlgorithm.parse(JwsAlgorithms.RS256); RSAKey rsaKey = rsaKey(publicKey); JWKSet jwkSet = new JWKSet(rsaKey); JWKSource jwkSource = new ImmutableJWKSet<>(jwkSet); JWSKeySelector<JWKContext> jwsKeySelector = new JWSVerificationKeySelector<>(algorithm, jwkSource); DefaultJWTProcessor jwtProcessor = new DefaultJWTProcessor<>(); jwtProcessor.setJWSKeySelector(jwsKeySelector); jwtProcessor.setJWTClaimsSetVerifier((claims, context) -> {}); this.jwtProcessor = jwtProcessor; this.reactiveJwkSource = new ReactiveJWKSourceAdapter(jwkSource); this.jwkSelectorFactory = new JWKSelectorFactory(algorithm); }
/** * Configures a {@link ReactiveJwtDecoder} that leverages the provided {@link RSAPublicKey} * * @param publicKey the public key to use. * @return the {@code JwtSpec} for additional configuration */ public JwtSpec publicKey(RSAPublicKey publicKey) { this.jwtDecoder = new NimbusReactiveJwtDecoder(publicKey); return this; }
@Override public ReactiveJwtDecoder createDecoder(ClientRegistration clientRegistration) { Assert.notNull(clientRegistration, "clientRegistration cannot be null"); return this.jwtDecoders.computeIfAbsent(clientRegistration.getRegistrationId(), key -> { if (!StringUtils.hasText(clientRegistration.getProviderDetails().getJwkSetUri())) { OAuth2Error oauth2Error = new OAuth2Error( MISSING_SIGNATURE_VERIFIER_ERROR_CODE, "Failed to find a Signature Verifier for Client Registration: '" + clientRegistration.getRegistrationId() + "'. Check to ensure you have configured the JwkSet URI.", null ); throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString()); } NimbusReactiveJwtDecoder jwtDecoder = new NimbusReactiveJwtDecoder( clientRegistration.getProviderDetails().getJwkSetUri()); OAuth2TokenValidator<Jwt> jwtValidator = this.jwtValidatorFactory.apply(clientRegistration); jwtDecoder.setJwtValidator(jwtValidator); return jwtDecoder; }); }
@Test public void decodeWhenRSAPublicKeyThenSuccess() throws Exception { byte[] bytes = Base64.getDecoder().decode("MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqL48v1clgFw+Evm145pmh8nRYiNt72Gupsshn7Qs8dxEydCRp1DPOV/PahPk1y2nvldBNIhfNL13JOAiJ6BTiF+2ICuICAhDArLMnTH61oL1Hepq8W1xpa9gxsnL1P51thvfmiiT4RTW57koy4xIWmIp8ZXXfYgdH2uHJ9R0CQBuYKe7nEOObjxCFWC8S30huOfW2cYtv0iB23h6w5z2fDLjddX6v/FXM7ktcokgpm3/XmvT/+bL6/GGwz9k6kJOyMTubecr+WT//le8ikY66zlplYXRQh6roFfFCL21Pt8xN5zrk+0AMZUnmi8F2S2ztSBmAVJ7H71ELXsURBVZpwIDAQAB"); RSAPublicKey publicKey = (RSAPublicKey) KeyFactory.getInstance("RSA") .generatePublic(new X509EncodedKeySpec(bytes)); this.decoder = new NimbusReactiveJwtDecoder(publicKey); String noKeyId = "eyJhbGciOiJSUzI1NiJ9.eyJzY29wZSI6IiIsImV4cCI6OTIyMzM3MjAwNjA5NjM3NX0.hNVuHSUkxdLZrDfqdmKcOi0ggmNaDuB4ZPxPtJl1gwBiXzIGN6Hwl24O2BfBZiHFKUTQDs4_RvzD71mEG3DvUrcKmdYWqIB1l8KNmxQLUDG-cAPIpJmRJgCh50tf8OhOE_Cb9E1HcsOUb47kT9iz-VayNBcmo6BmyZLdEGhsdGBrc3Mkz2dd_0PF38I2Hf_cuSjn9gBjFGtiPEXJvob3PEjVTSx_zvodT8D9p3An1R3YBZf5JSd1cQisrXgDX2k1Jmf7UKKWzgfyCgnEtRWWbsUdPqo3rSEY9GDC1iSQXsFTTC1FT_JJDkwzGf011fsU5O_Ko28TARibmKTCxAKNRQ"; assertThatCode(() -> this.decoder.decode(noKeyId).block()) .doesNotThrowAnyException(); }
@Test public void decodeWhenMessageReadScopeThenSuccess() { Jwt jwt = this.decoder.decode(this.messageReadToken).block(); assertThat(jwt.getClaims().get("scope")).isEqualTo("message:read"); }
/** * Configures a {@link ReactiveJwtDecoder} that leverages the provided {@link RSAPublicKey} * * @param publicKey the public key to use. * @return the {@code JwtSpec} for additional configuration */ public JwtSpec publicKey(RSAPublicKey publicKey) { this.jwtDecoder = new NimbusReactiveJwtDecoder(publicKey); return this; }
/** * Creates a {@link ReactiveJwtDecoder} using the provided * <a href="http://openid.net/specs/openid-connect-core-1_0.html#IssuerIdentifier">Issuer</a> by making an * <a href="https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfigurationRequest">OpenID Provider * Configuration Request</a> and using the values in the * <a href="https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfigurationResponse">OpenID * Provider Configuration Response</a> to initialize the {@link ReactiveJwtDecoder}. * * @param oidcIssuerLocation the <a href="http://openid.net/specs/openid-connect-core-1_0.html#IssuerIdentifier">Issuer</a> * @return a {@link ReactiveJwtDecoder} that was initialized by the OpenID Provider Configuration. */ public static ReactiveJwtDecoder fromOidcIssuerLocation(String oidcIssuerLocation) { Map<String, Object> openidConfiguration = getOpenidConfiguration(oidcIssuerLocation); String metadataIssuer = "(unavailable)"; if (openidConfiguration.containsKey("issuer")) { metadataIssuer = openidConfiguration.get("issuer").toString(); } if (!oidcIssuerLocation.equals(metadataIssuer)) { throw new IllegalStateException("The Issuer \"" + metadataIssuer + "\" provided in the OpenID Configuration " + "did not match the requested issuer \"" + oidcIssuerLocation + "\""); } OAuth2TokenValidator<Jwt> jwtValidator = JwtValidators.createDefaultWithIssuer(oidcIssuerLocation); NimbusReactiveJwtDecoder jwtDecoder = new NimbusReactiveJwtDecoder(openidConfiguration.get("jwks_uri").toString()); jwtDecoder.setJwtValidator(jwtValidator); return jwtDecoder; }