Skip to content

Simplify Casting with ReactiveJwtDecoders #15773

Closed
@rhanton

Description

@rhanton

Expected Behavior

It would be great if the ReactiveJwtDecoders.java helper methods exposed the underlying NimbusReactiveJwtDecoder & associated methods rather than only the interface just as the non-reactive version (JwtDecoders.java) does. This makes it easier to override the default Jwt validator(s) when necessary.

For example, the decompiled code today in JwtDecoders.java looks like this:

  public static <T extends JwtDecoder> T fromOidcIssuerLocation(String oidcIssuerLocation) {
    Assert.hasText(oidcIssuerLocation, "oidcIssuerLocation cannot be empty");
    Map<String, Object> configuration = JwtDecoderProviderConfigurationUtils.getConfigurationForOidcIssuerLocation(oidcIssuerLocation);
    return withProviderConfiguration(configuration, oidcIssuerLocation);
  }

  public static <T extends JwtDecoder> T fromIssuerLocation(String issuer) {
    Assert.hasText(issuer, "issuer cannot be empty");
    NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder.withIssuerLocation(issuer).build();
    OAuth2TokenValidator<Jwt> jwtValidator = JwtValidators.createDefaultWithIssuer(issuer);
    jwtDecoder.setJwtValidator(jwtValidator);
    return jwtDecoder;
  }

It would be awesome to see similar code in ReactiveJwtDecoders that returns anything extending ReactiveJwtDecoder instead of just the interface so that it can be tweaked outside of the default use-case if needed. Ideal code would look like:

  public static <T extends ReactiveJwtDecoder> T fromOidcIssuerLocation(String oidcIssuerLocation) {
    Assert.hasText(oidcIssuerLocation, "oidcIssuerLocation cannot be empty");
    Map<String, Object> configuration = JwtDecoderProviderConfigurationUtils.getConfigurationForOidcIssuerLocation(oidcIssuerLocation);
    return withProviderConfiguration(configuration, oidcIssuerLocation);
  }

  public static <T extends ReactiveJwtDecoder> T fromIssuerLocation(String issuer) {
    Assert.hasText(issuer, "issuer cannot be empty");
    Map<String, Object> configuration = JwtDecoderProviderConfigurationUtils.getConfigurationForIssuerLocation(issuer);
    return withProviderConfiguration(configuration, issuer);
  }

  private static <T extends ReactiveJwtDecoder> T withProviderConfiguration(Map<String, Object> configuration, String issuer) {
    JwtDecoderProviderConfigurationUtils.validateIssuer(configuration, issuer);
    OAuth2TokenValidator<Jwt> jwtValidator = JwtValidators.createDefaultWithIssuer(issuer);
    String jwkSetUri = configuration.get("jwks_uri").toString();
    NimbusReactiveJwtDecoder jwtDecoder = NimbusReactiveJwtDecoder.withJwkSetUri(jwkSetUri).jwtProcessorCustomizer(ReactiveJwtDecoderProviderConfigurationUtils::addJWSAlgorithms).build();
    jwtDecoder.setJwtValidator(jwtValidator);
    return jwtDecoder;
  }

Current Behavior

ReactiveJwtDecoders.java currently looks like this:

  public static ReactiveJwtDecoder fromOidcIssuerLocation(String oidcIssuerLocation) {
    Assert.hasText(oidcIssuerLocation, "oidcIssuerLocation cannot be empty");
    Map<String, Object> configuration = JwtDecoderProviderConfigurationUtils.getConfigurationForOidcIssuerLocation(oidcIssuerLocation);
    return withProviderConfiguration(configuration, oidcIssuerLocation);
  }

  public static ReactiveJwtDecoder fromIssuerLocation(String issuer) {
    Assert.hasText(issuer, "issuer cannot be empty");
    Map<String, Object> configuration = JwtDecoderProviderConfigurationUtils.getConfigurationForIssuerLocation(issuer);
    return withProviderConfiguration(configuration, issuer);
  }

and the only accessible method on ReactiveJwtDecoder is .decode(). This means that because JwtDecoderProviderConfigurationUtils.java is not publicly accessible, I'll need to re-write a good portion of its functionality in my own code to essentially rebuild what is happening in the ReactiveJwtDecoders method with some customization of the JWT validation (in our case we want to allow for multiple allowed issuers which allows us to have a CNAMEd issuer + non-CNAMEd or do things like switch issuer without downtime).

Context

The default JwtValidator with issuer provided validates the Jwt Timestamp (with 60s clock skew) + the Issuer (only). We want to validate:

  • Timestamp (with customizable skew)
  • Issuer (as "one-of" many possible issuers, configurable)
  • Audience

The current reactive code makes it pretty difficult to both harness the "smarts" inside JwtDecoderProviderConfigurationUtils that uses OpenID provider config or an Authorization Server Metadata request to get provider information AND customize the JWT validations, while the WebMVC version of JwtDecoders.java makes this fairly easy.

Metadata

Metadata

Assignees

Labels

in: oauth2An issue in OAuth2 modules (oauth2-core, oauth2-client, oauth2-resource-server, oauth2-jose)status: duplicateA duplicate of another issuetype: enhancementA general enhancement

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions