1

Our authorization server (Auth0) reportedly does not change the JWK set (maybe ever?). For performance, we'd like to cache the keys from the remote auth server for much longer than the hard-coded default of 5 minutes in DefaultJWKSetCache, since these keys are required to validate the tokens for all requests, and each time they are requested adds significant latency. Note the JWT decoder library class is final.

The path of least resistance to reduce the number of requests to the authorization server seems to be to proxy with a local route that will handle the caching. Is there a better way?

Version: Spring Boot 2.2.5 (Spring 5.2)

// Relevant imports (omitted) from:
// import org.springframework.security.oauth2.core
// import org.springframework.security.oauth2.jwt

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Value("${auth0.audience}")
    private String audience;
    @Value("${spring.security.oauth2.resourceserver.jwt.issuer-uri}")
    private String issuer;

    @Override
    public void configure(HttpSecurity http) throws Exception {
        // @formatter:off
        http
            .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
            .authorizeRequests()
                .antMatchers("/api/**").authenticated()
                .anyRequest().permitAll()
                .and()
            .cors()
                .and()
            .csrf()
                .disable()
            .oauth2ResourceServer()
                .jwt();
        // @formatter:on
    }

    @Bean
    JwtDecoder jwtDecoder() {
        NimbusJwtDecoder jwtDecoder = (NimbusJwtDecoder) JwtDecoders.fromOidcIssuerLocation(issuer);

        OAuth2TokenValidator<Jwt> audienceValidator = new AudienceValidator(audience);
        OAuth2TokenValidator<Jwt> withIssuer = JwtValidators.createDefaultWithIssuer(issuer);
        OAuth2TokenValidator<Jwt> withAudience = new DelegatingOAuth2TokenValidator<>(withIssuer, audienceValidator);

        jwtDecoder.setJwtValidator(withAudience);

        return jwtDecoder;
    }
}
Tomanow
  • 7,247
  • 3
  • 24
  • 52

1 Answers1

3

This issue came up again and I ended up finding a helpful answer (slightly different syntax for DefaultJWKSetCache) that got me close enough to resolve with this:

    @Bean
    JwtDecoder jwtDecoder() {
        JWSKeySelector<SecurityContext> jwsKeySelector;
        try {
            URL jwksUrl = new URL(authConfiguration.getJwksUrl());
            JWKSetCache jwkSetCache = new DefaultJWKSetCache(authConfiguration.getJwksCacheTtlMins(), TimeUnit.MINUTES);
            RemoteJWKSet<SecurityContext> jwkSet = new RemoteJWKSet<>(jwksUrl, null, jwkSetCache);
            jwsKeySelector = JWSAlgorithmFamilyJWSKeySelector.fromJWKSource(jwkSet);
        } catch (KeySourceException | MalformedURLException e) {
            throw new InvalidConfigurationException(e.getMessage());
        }

        DefaultJWTProcessor<SecurityContext> jwtProcessor = new DefaultJWTProcessor<>();
        jwtProcessor.setJWSKeySelector(jwsKeySelector);
        NimbusJwtDecoder jwtDecoder = new NimbusJwtDecoder(jwtProcessor);

        OAuth2TokenValidator<Jwt> audienceValidator = new AudienceValidator(authConfiguration.getAudience());
        OAuth2TokenValidator<Jwt> withIssuer = JwtValidators.createDefaultWithIssuer(authConfiguration.getIssuer());
        OAuth2TokenValidator<Jwt> withAudience = new DelegatingOAuth2TokenValidator<>(withIssuer, audienceValidator);
        
        jwtDecoder.setJwtValidator(withAudience);

        return jwtDecoder;
    }
Tomanow
  • 7,247
  • 3
  • 24
  • 52