@Test public void decodeWhenExpClaimNullThenDoesNotThrowException() throws Exception { NimbusJwtDecoderJwkSupport jwtDecoder = new NimbusJwtDecoderJwkSupport(JWK_SET_URL); jwtDecoder.setRestOperations(mockJwkSetResponse(JWK_SET)); jwtDecoder.setClaimSetConverter(map -> { Map<String, Object> claims = new HashMap<>(map); claims.remove(JwtClaimNames.EXP); return claims; }); assertThatCode(() -> jwtDecoder.decode(SIGNED_JWT)).doesNotThrowAnyException(); }
/** * Sets the {@link RestOperations} used when requesting the JSON Web Key (JWK) Set. * * @since 5.1 * @param restOperations the {@link RestOperations} used when requesting the JSON Web Key (JWK) Set */ public final void setRestOperations(RestOperations restOperations) { Assert.notNull(restOperations, "restOperations cannot be null"); this.jwtProcessorBuilder = this.jwtProcessorBuilder.restOperations(restOperations); this.delegate = makeDelegate(); } }
@Test public void decodeWhenUsingSignedJwtThenReturnsClaimsGivenByClaimSetConverter() throws Exception { try ( MockWebServer server = new MockWebServer() ) { server.enqueue(new MockResponse().setBody(JWK_SET)); String jwkSetUrl = server.url("/.well-known/jwks.json").toString(); NimbusJwtDecoderJwkSupport decoder = new NimbusJwtDecoderJwkSupport(jwkSetUrl); Converter<Map<String, Object>, Map<String, Object>> claimSetConverter = mock(Converter.class); when(claimSetConverter.convert(any(Map.class))).thenReturn(Collections.singletonMap("custom", "value")); decoder.setClaimSetConverter(claimSetConverter); Jwt jwt = decoder.decode(SIGNED_JWT); assertThat(jwt.getClaims().size()).isEqualTo(1); assertThat(jwt.getClaims().get("custom")).isEqualTo("value"); } }
@Test public void decodeWhenCustomRestOperationsSetThenUsed() throws Exception { try ( MockWebServer server = new MockWebServer() ) { server.enqueue(new MockResponse().setBody(JWK_SET)); String jwkSetUrl = server.url("/.well-known/jwks.json").toString(); NimbusJwtDecoderJwkSupport jwtDecoder = new NimbusJwtDecoderJwkSupport(jwkSetUrl); RestTemplate restTemplate = spy(new RestTemplate()); jwtDecoder.setRestOperations(restTemplate); assertThatCode(() -> jwtDecoder.decode(SIGNED_JWT)).doesNotThrowAnyException(); verify(restTemplate).exchange(any(RequestEntity.class), eq(String.class)); server.shutdown(); } }
@Test public void decodeWhenJwtFailsValidationThenReturnsCorrespondingErrorMessage() throws Exception { try ( MockWebServer server = new MockWebServer() ) { server.enqueue(new MockResponse().setBody(JWK_SET)); String jwkSetUrl = server.url("/.well-known/jwks.json").toString(); NimbusJwtDecoderJwkSupport decoder = new NimbusJwtDecoderJwkSupport(jwkSetUrl); OAuth2Error failure = new OAuth2Error("mock-error", "mock-description", "mock-uri"); OAuth2TokenValidator<Jwt> jwtValidator = mock(OAuth2TokenValidator.class); when(jwtValidator.validate(any(Jwt.class))).thenReturn(OAuth2TokenValidatorResult.failure(failure)); decoder.setJwtValidator(jwtValidator); assertThatCode(() -> decoder.decode(SIGNED_JWT)) .isInstanceOf(JwtValidationException.class) .hasMessageContaining("mock-description"); } }
@Bean @ConditionalOnMissingBean public JwtDecoder iapJwtDecoder(IapAuthenticationProperties properties, @Qualifier("iapJwtDelegatingValidator") DelegatingOAuth2TokenValidator<Jwt> validator) { NimbusJwtDecoderJwkSupport jwkSupport = new NimbusJwtDecoderJwkSupport(properties.getRegistry(), properties.getAlgorithm()); jwkSupport.setJwtValidator(validator); return jwkSupport; } }
@Test public void constructorWhenJwsAlgorithmIsNullThenThrowIllegalArgumentException() { assertThatThrownBy(() -> new NimbusJwtDecoderJwkSupport(JWK_SET_URL, null)) .isInstanceOf(IllegalArgumentException.class); }
@Test public void decodeWhenJwkResponseIsMalformedThenReturnsStockException() throws Exception { try ( MockWebServer server = new MockWebServer() ) { server.enqueue(new MockResponse().setBody(MALFORMED_JWK_SET)); String jwkSetUrl = server.url("/.well-known/jwks.json").toString(); NimbusJwtDecoderJwkSupport jwtDecoder = new NimbusJwtDecoderJwkSupport(jwkSetUrl); assertThatCode(() -> jwtDecoder.decode(SIGNED_JWT)) .isInstanceOf(JwtException.class) .hasMessage("An error occurred while attempting to decode the Jwt: Malformed Jwk set"); server.shutdown(); } }
@Bean @ConditionalOnMissingBean JwtDecoder jwtDecoder(OAuth2ResourceServerProperties oAuth2ResourceServerProperties, OktaOAuth2Properties oktaOAuth2Properties) { List<OAuth2TokenValidator<Jwt>> validators = new ArrayList<>(); validators.add(new JwtTimestampValidator()); validators.add(new JwtIssuerValidator(oAuth2ResourceServerProperties.getJwt().getIssuerUri())); validators.add(token -> { Set<String> expectedAudience = new HashSet<>(); expectedAudience.add(oktaOAuth2Properties.getAudience()); return !Collections.disjoint(token.getAudience(), expectedAudience) ? OAuth2TokenValidatorResult.success() : OAuth2TokenValidatorResult.failure(INVALID_AUDIENCE); }); OAuth2TokenValidator<Jwt> validator = new DelegatingOAuth2TokenValidator<>(validators); NimbusJwtDecoderJwkSupport decoder = new NimbusJwtDecoderJwkSupport(oAuth2ResourceServerProperties.getJwt().getJwkSetUri()); decoder.setJwtValidator(validator); decoder.setRestOperations(restOperations()); return decoder; }
@Override public Jwt decode(String token) throws JwtException { JWT jwt = this.parse(token); if (jwt instanceof SignedJWT) { Jwt createdJwt = this.createJwt(token, jwt); return this.validateJwt(createdJwt); } throw new JwtException("Unsupported algorithm of " + jwt.getHeader().getAlgorithm()); }
@Test public void setRestOperationsWhenNullThenThrowIllegalArgumentException() { Assertions.assertThatThrownBy(() -> this.jwtDecoder.setRestOperations(null)) .isInstanceOf(IllegalArgumentException.class); }
@Test public void decodeWhenJwtInvalidThenThrowJwtException() { assertThatThrownBy(() -> this.jwtDecoder.decode("invalid")) .isInstanceOf(JwtException.class); }
@Test public void decodeWhenJwtValidationHasTwoErrorsThenJwtExceptionMessageShowsFirstError() throws Exception { try ( MockWebServer server = new MockWebServer() ) { server.enqueue(new MockResponse().setBody(JWK_SET)); String jwkSetUrl = server.url("/.well-known/jwks.json").toString(); NimbusJwtDecoderJwkSupport decoder = new NimbusJwtDecoderJwkSupport(jwkSetUrl); OAuth2Error firstFailure = new OAuth2Error("mock-error", "mock-description", "mock-uri"); OAuth2Error secondFailure = new OAuth2Error("another-error", "another-description", "another-uri"); OAuth2TokenValidatorResult result = OAuth2TokenValidatorResult.failure(firstFailure, secondFailure); OAuth2TokenValidator<Jwt> jwtValidator = mock(OAuth2TokenValidator.class); when(jwtValidator.validate(any(Jwt.class))).thenReturn(result); decoder.setJwtValidator(jwtValidator); assertThatCode(() -> decoder.decode(SIGNED_JWT)) .isInstanceOf(JwtValidationException.class) .hasMessageContaining("mock-description") .hasFieldOrPropertyWithValue("errors", Arrays.asList(firstFailure, secondFailure)); } }
/** * Creates a {@link JwtDecoder} 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 JwtDecoder}. * * @param oidcIssuerLocation the <a href="http://openid.net/specs/openid-connect-core-1_0.html#IssuerIdentifier">Issuer</a> * @return a {@link JwtDecoder} that was initialized by the OpenID Provider Configuration. */ public static JwtDecoder 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); NimbusJwtDecoderJwkSupport jwtDecoder = new NimbusJwtDecoderJwkSupport(openidConfiguration.get("jwks_uri").toString()); jwtDecoder.setJwtValidator(jwtValidator); return jwtDecoder; }
@Test public void constructorWhenJwkSetUrlInvalidThenThrowIllegalArgumentException() { assertThatThrownBy(() -> new NimbusJwtDecoderJwkSupport("invalid.com")) .isInstanceOf(IllegalArgumentException.class); }
@Test public void decodeWhenJwtIsMalformedThenReturnsStockException() throws Exception { try ( MockWebServer server = new MockWebServer() ) { server.enqueue(new MockResponse().setBody(JWK_SET)); String jwkSetUrl = server.url("/.well-known/jwks.json").toString(); NimbusJwtDecoderJwkSupport jwtDecoder = new NimbusJwtDecoderJwkSupport(jwkSetUrl); assertThatCode(() -> jwtDecoder.decode(MALFORMED_JWT)) .isInstanceOf(JwtException.class) .hasMessage("An error occurred while attempting to decode the Jwt: Malformed payload"); server.shutdown(); } }
@Test public void decodeWhenPlainJwtThenExceptionDoesNotMentionClass() { assertThatCode(() -> this.jwtDecoder.decode(UNSIGNED_JWT)) .isInstanceOf(JwtException.class) .hasMessageContaining("Unsupported algorithm of none"); }
@Test public void constructorWhenJwkSetUrlIsNullThenThrowIllegalArgumentException() { assertThatThrownBy(() -> new NimbusJwtDecoderJwkSupport(null)) .isInstanceOf(IllegalArgumentException.class); }
@Test public void decodeWhenJwkEndpointIsUnresponsiveThenReturnsJwtException() throws Exception { try ( MockWebServer server = new MockWebServer() ) { server.enqueue(new MockResponse().setBody(MALFORMED_JWK_SET)); String jwkSetUrl = server.url("/.well-known/jwks.json").toString(); NimbusJwtDecoderJwkSupport jwtDecoder = new NimbusJwtDecoderJwkSupport(jwkSetUrl); assertThatCode(() -> jwtDecoder.decode(SIGNED_JWT)) .isInstanceOf(JwtException.class) .hasMessageContaining("An error occurred while attempting to decode the Jwt"); server.shutdown(); } }
/** * Constructs a {@code NimbusJwtDecoderJwkSupport} using the provided parameters. * * @param jwkSetUrl the JSON Web Key (JWK) Set {@code URL} * @param jwsAlgorithm the JSON Web Algorithm (JWA) used for verifying the digital signatures */ public NimbusJwtDecoderJwkSupport(String jwkSetUrl, String jwsAlgorithm) { Assert.hasText(jwkSetUrl, "jwkSetUrl cannot be empty"); Assert.hasText(jwsAlgorithm, "jwsAlgorithm cannot be empty"); this.jwtProcessorBuilder = withJwkSetUri(jwkSetUrl).jwsAlgorithm(jwsAlgorithm); this.delegate = makeDelegate(); }