Spring Security + OAuth2 Resource Server: How to Validate JWTs Like a Pro
OAuth2 and JWT (JSON Web Tokens) have become the standard for securing APIs in modern microservices and web applications.
When building a resource server with Spring Security,
properly validating incoming JWTs is essential for security and performance.
In this article, we’ll deep dive into how to configure JwtDecoder, introspect tokens when needed, and enforce scopes to achieve robust authorization.
1. What is a JWT and Why Validate It?
A JWT is a signed token that contains claims about the user or client, used to authenticate and authorize API requests.
Validation ensures the token’s signature is valid, it hasn’t expired, and contains the correct claims for the requested resource.
2. Setting Up Spring Security OAuth2 Resource Server
First, add the dependency:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-oauth2-resource-server</artifactId> </dependency>
Then configure your application to enable JWT validation:
import org.springframework.context.annotation.Bean;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;
import org.springframework.security.web.SecurityFilterChain;
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/admin/**").hasAuthority("SCOPE_admin")
.anyRequest().authenticated()
)
.oauth2ResourceServer(oauth2 -> oauth2
.jwt(jwt -> jwt.jwtAuthenticationConverter(jwtAuthenticationConverter()))
);
return http.build();
}
private JwtAuthenticationConverter jwtAuthenticationConverter() {
JwtAuthenticationConverter converter = new JwtAuthenticationConverter();
// Customize converter to map scopes to authorities if needed
return converter;
}
}
3. Configuring JwtDecoder
Spring Security provides default JWT decoding from the issuer’s JWK set URL.
If you want to customize validation logic or load keys manually, define a JwtDecoder bean:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
import java.security.interfaces.RSAPublicKey;
@Configuration
public class JwtDecoderConfig {
@Bean
public JwtDecoder jwtDecoder(RSAPublicKey publicKey) {
return NimbusJwtDecoder.withPublicKey(publicKey).build();
}
}
This is useful when you manage your own keys instead of relying on an authorization server’s metadata endpoint.
4. Using Introspection for Opaque Tokens
Sometimes, you get opaque tokens instead of JWTs. In that case, introspection is required to verify the token’s validity.
Spring Security supports this with OpaqueTokenIntrospector.
import org.springframework.context.annotation.Bean;
import org.springframework.security.oauth2.server.resource.introspection.OpaqueTokenIntrospector;
import org.springframework.security.oauth2.server.resource.introspection.NimbusOpaqueTokenIntrospector;
@Bean
public OpaqueTokenIntrospector introspector() {
return new NimbusOpaqueTokenIntrospector(
"https://auth-server.com/introspect",
"client-id",
"client-secret"
);
}
Use introspection if JWT validation is not an option or you want centralized token verification.
5. Enforcing Scopes and Claims
One powerful feature in Spring Security is enforcing scopes as authorities.
With the configuration above, hasAuthority("SCOPE_admin") checks that the token contains the admin scope.
You can also extract custom claims from the JWT using a customized JwtAuthenticationConverter:
import org.springframework.core.convert.converter.Converter;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;
import org.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter;
import java.util.Collection;
public class CustomJwtAuthConverter extends JwtAuthenticationConverter {
@Override
protected Collection<GrantedAuthority> extractAuthorities(Jwt jwt) {
JwtGrantedAuthoritiesConverter defaultConverter = new JwtGrantedAuthoritiesConverter();
Collection<GrantedAuthority> authorities = defaultConverter.convert(jwt);
// Example: Add custom claim-based authority
if ("admin".equals(jwt.getClaimAsString("role"))) {
authorities.add(() -> "ROLE_ADMIN");
}
return authorities;
}
}
Then inject this converter in your security config:
oauth2 .jwt(jwt -> jwt.jwtAuthenticationConverter(new CustomJwtAuthConverter()));
6. Developer Opinions & Best Practices
- Prefer JWTs when possible. They are stateless, fast, and scalable since no introspection calls are needed.
- Use introspection if tokens are opaque or you want centralized control. This adds a network roundtrip but improves revocation and validation.
- Carefully map scopes to authorities. Overly permissive roles can lead to security issues.
- Keep token size minimal. Too many claims increase overhead and processing time.
- Regularly rotate keys and validate signature algorithms. Avoid deprecated or weak algorithms.
7. Further Reading
- Spring Boot OAuth2 Tutorial
- Spring Security JWT Resource Server Docs
- OAuth 2.0 Framework
- JWT Introduction and Tools
Proper JWT validation is critical for secure APIs. Spring Security’s resource server support gives you a flexible and powerful way to achieve this with minimal code.




