Validating JWTs on every request

Spring Security · lesson 13 of 31 · 4 min read

Accept bearer tokens with the built-in resource server instead of writing a custom filter.

Open this lesson in the learning hub

Key points

  • Do not write a custom JWT filter. oauth2ResourceServer installs BearerTokenAuthenticationFilter for you.
  • It checks signature, exp, nbf and issuer, then puts a JwtAuthenticationToken in the context.
  • With an external provider set issuer-uri. Keys come from the JWKS endpoint and are cached and rotated automatically.
  • A scope claim becomes SCOPE_read authorities. Map a custom claim with JwtAuthenticationConverter.
  • Rejections return 401 with a WWW-Authenticate header naming the failed check. Read it before debugging blind.

Example

@Bean
SecurityFilterChain api(HttpSecurity http, JwtAuthenticationConverter converter) throws Exception {
    return http
        .securityMatcher("/api/**")
        .csrf(csrf -> csrf.disable())
        .sessionManagement(s -> s.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
        .authorizeHttpRequests(a -> a
            .requestMatchers("/api/auth/**").permitAll()
            .anyRequest().authenticated())
        .oauth2ResourceServer(o -> o.jwt(jwt -> jwt.jwtAuthenticationConverter(converter)))
        .build();
}

@Bean
JwtDecoder jwtDecoder(RSAPublicKey publicKey) {
    return NimbusJwtDecoder.withPublicKey(publicKey).build();
}

@Bean
JwtAuthenticationConverter jwtAuthenticationConverter() {
    JwtGrantedAuthoritiesConverter authorities = new JwtGrantedAuthoritiesConverter();
    authorities.setAuthoritiesClaimName("roles");
    authorities.setAuthorityPrefix("");   // the claim already contains ROLE_ values

    JwtAuthenticationConverter converter = new JwtAuthenticationConverter();
    converter.setJwtGrantedAuthoritiesConverter(authorities);
    return converter;
}

The resource server already does JWT validation properly — plug it in, do not reinvent it.

This is a reading copy. The full lesson — with the visual explainer, the interactive lab and a Run button for the code — lives in the Spring Security course, and every lesson in it is listed on the Spring Security contents page.