diff --git a/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/JwtDecoders.java b/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/JwtDecoders.java index 295bfb8a98..cb83524fcb 100644 --- a/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/JwtDecoders.java +++ b/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/JwtDecoders.java @@ -20,6 +20,7 @@ import org.springframework.security.oauth2.core.OAuth2TokenValidator; import org.springframework.util.Assert; +import org.springframework.web.client.RestOperations; /** * Allows creating a {@link JwtDecoder} from an T fromOidcIssuerLocation(String oidcIssuerLocation) { + return fromOidcIssuerLocation(oidcIssuerLocation, null); + } + + /** + * Creates a {@link JwtDecoder} using the provided Issuer + * by making an OpenID + * Provider Configuration Request and using the values in the OpenID + * Provider Configuration Response to initialize the {@link JwtDecoder}. + * @param oidcIssuerLocation the Issuer + * @param restOperations customized {@link RestOperations} + * @return a {@link JwtDecoder} that was initialized by the OpenID Provider + * Configuration. + */ + public static T fromOidcIssuerLocation(String oidcIssuerLocation, + RestOperations restOperations) { Assert.hasText(oidcIssuerLocation, "oidcIssuerLocation cannot be empty"); Map configuration = JwtDecoderProviderConfigurationUtils .getConfigurationForOidcIssuerLocation(oidcIssuerLocation); - return (T) withProviderConfiguration(configuration, oidcIssuerLocation); + return (T) withProviderConfiguration(configuration, oidcIssuerLocation, restOperations); } /** @@ -88,8 +108,46 @@ public static T fromOidcIssuerLocation(String oidcIssuerL */ @SuppressWarnings("unchecked") public static T fromIssuerLocation(String issuer) { + return fromIssuerLocation(issuer, null); + } + + /** + * Creates a {@link JwtDecoder} using the provided Issuer + * by querying three different discovery endpoints serially, using the values in the + * first successful response to initialize. If an endpoint returns anything other than + * a 200 or a 4xx, the method will exit without attempting subsequent endpoints. + * + * The three endpoints are computed as follows, given that the {@code issuer} is + * composed of a {@code host} and a {@code path}: + * + *
    + *
  1. {@code host/.well-known/openid-configuration/path}, as defined in + * RFC 8414's Compatibility + * Notes.
  2. + *
  3. {@code issuer/.well-known/openid-configuration}, as defined in + * OpenID Provider Configuration.
  4. + *
  5. {@code host/.well-known/oauth-authorization-server/path}, as defined in + * Authorization Server + * Metadata Request.
  6. + *
+ * + * Note that the second endpoint is the equivalent of calling + * {@link JwtDecoders#fromOidcIssuerLocation(String)} + * @param issuer the Issuer + * @param restOperations customized {@link RestOperations} + * @return a {@link JwtDecoder} that was initialized by one of the described endpoints + */ + @SuppressWarnings("unchecked") + public static T fromIssuerLocation(String issuer, RestOperations restOperations) { Assert.hasText(issuer, "issuer cannot be empty"); - NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder.withIssuerLocation(issuer).build(); + NimbusJwtDecoder.JwkSetUriJwtDecoderBuilder builder = NimbusJwtDecoder.withIssuerLocation(issuer); + if (restOperations != null) { + builder = builder.restOperations(restOperations); + } + NimbusJwtDecoder jwtDecoder = builder.build(); OAuth2TokenValidator jwtValidator = JwtValidators.createDefaultWithIssuer(issuer); jwtDecoder.setJwtValidator(jwtValidator); return (T) jwtDecoder; @@ -104,15 +162,20 @@ public static T fromIssuerLocation(String issuer) { * @param configuration the configuration values * @param issuer the Issuer + * @param restOperations customized {@link RestOperations} * @return {@link JwtDecoder} */ - private static JwtDecoder withProviderConfiguration(Map configuration, String issuer) { + private static JwtDecoder withProviderConfiguration(Map configuration, String issuer, + RestOperations restOperations) { JwtDecoderProviderConfigurationUtils.validateIssuer(configuration, issuer); OAuth2TokenValidator jwtValidator = JwtValidators.createDefaultWithIssuer(issuer); String jwkSetUri = configuration.get("jwks_uri").toString(); - NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder.withJwkSetUri(jwkSetUri) - .jwtProcessorCustomizer(JwtDecoderProviderConfigurationUtils::addJWSAlgorithms) - .build(); + NimbusJwtDecoder.JwkSetUriJwtDecoderBuilder builder = NimbusJwtDecoder.withJwkSetUri(jwkSetUri) + .jwtProcessorCustomizer(JwtDecoderProviderConfigurationUtils::addJWSAlgorithms); + if (restOperations != null) { + builder = builder.restOperations(restOperations); + } + NimbusJwtDecoder jwtDecoder = builder.build(); jwtDecoder.setJwtValidator(jwtValidator); return jwtDecoder; } diff --git a/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/ReactiveJwtDecoders.java b/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/ReactiveJwtDecoders.java index b6d37496c5..6b7976f062 100644 --- a/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/ReactiveJwtDecoders.java +++ b/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/ReactiveJwtDecoders.java @@ -20,6 +20,7 @@ import org.springframework.security.oauth2.core.OAuth2TokenValidator; import org.springframework.util.Assert; +import org.springframework.web.reactive.function.client.WebClient; /** * Allows creating a {@link ReactiveJwtDecoder} from an T fromOidcIssuerLocation(String oidcIssuerLocation) { + return fromOidcIssuerLocation(oidcIssuerLocation, null); + } + + /** + * Creates a {@link ReactiveJwtDecoder} using the provided Issuer + * by making an OpenID + * Provider Configuration Request and using the values in the OpenID + * Provider Configuration Response to initialize the {@link ReactiveJwtDecoder}. + * @param oidcIssuerLocation the Issuer + * @param webClient customized {@link WebClient} + * @return a {@link ReactiveJwtDecoder} that was initialized by the OpenID Provider + * Configuration. + */ + public static T fromOidcIssuerLocation(String oidcIssuerLocation, + WebClient webClient) { Assert.hasText(oidcIssuerLocation, "oidcIssuerLocation cannot be empty"); Map configuration = JwtDecoderProviderConfigurationUtils .getConfigurationForOidcIssuerLocation(oidcIssuerLocation); - return (T) withProviderConfiguration(configuration, oidcIssuerLocation); + return (T) withProviderConfiguration(configuration, oidcIssuerLocation, webClient); } /** @@ -88,10 +108,45 @@ public static T fromOidcIssuerLocation(String oid */ @SuppressWarnings("unchecked") public static T fromIssuerLocation(String issuer) { + return fromIssuerLocation(issuer, null); + } + + /** + * Creates a {@link ReactiveJwtDecoder} using the provided Issuer + * by querying three different discovery endpoints serially, using the values in the + * first successful response to initialize. If an endpoint returns anything other than + * a 200 or a 4xx, the method will exit without attempting subsequent endpoints. + * + * The three endpoints are computed as follows, given that the {@code issuer} is + * composed of a {@code host} and a {@code path}: + * + *
    + *
  1. {@code host/.well-known/openid-configuration/path}, as defined in + * RFC 8414's Compatibility + * Notes.
  2. + *
  3. {@code issuer/.well-known/openid-configuration}, as defined in + * OpenID Provider Configuration.
  4. + *
  5. {@code host/.well-known/oauth-authorization-server/path}, as defined in + * Authorization Server + * Metadata Request.
  6. + *
+ * + * Note that the second endpoint is the equivalent of calling + * {@link ReactiveJwtDecoders#fromOidcIssuerLocation(String)} + * @param issuer the Issuer + * @param webClient customized {@link WebClient} + * @return a {@link ReactiveJwtDecoder} that was initialized by one of the described + * endpoints + */ + @SuppressWarnings("unchecked") + public static T fromIssuerLocation(String issuer, WebClient webClient) { Assert.hasText(issuer, "issuer cannot be empty"); Map configuration = JwtDecoderProviderConfigurationUtils .getConfigurationForIssuerLocation(issuer); - return (T) withProviderConfiguration(configuration, issuer); + return (T) withProviderConfiguration(configuration, issuer, webClient); } /** @@ -103,15 +158,21 @@ public static T fromIssuerLocation(String issuer) * @param configuration the configuration values * @param issuer the Issuer + * @param webClient customized {@link WebClient} * @return {@link ReactiveJwtDecoder} */ - private static ReactiveJwtDecoder withProviderConfiguration(Map configuration, String issuer) { + private static ReactiveJwtDecoder withProviderConfiguration(Map configuration, String issuer, + WebClient webClient) { JwtDecoderProviderConfigurationUtils.validateIssuer(configuration, issuer); OAuth2TokenValidator jwtValidator = JwtValidators.createDefaultWithIssuer(issuer); String jwkSetUri = configuration.get("jwks_uri").toString(); - NimbusReactiveJwtDecoder jwtDecoder = NimbusReactiveJwtDecoder.withJwkSetUri(jwkSetUri) - .jwtProcessorCustomizer(ReactiveJwtDecoderProviderConfigurationUtils::addJWSAlgorithms) - .build(); + NimbusReactiveJwtDecoder.JwkSetUriReactiveJwtDecoderBuilder builder = NimbusReactiveJwtDecoder + .withJwkSetUri(jwkSetUri) + .jwtProcessorCustomizer(ReactiveJwtDecoderProviderConfigurationUtils::addJWSAlgorithms); + if (webClient != null) { + builder = builder.webClient(webClient); + } + NimbusReactiveJwtDecoder jwtDecoder = builder.build(); jwtDecoder.setJwtValidator(jwtValidator); return jwtDecoder; }