API Security Deep Dive: OAuth2, JWT, and Spring Security 7 Editorial Team, January 5, 2026January 5, 2026 In today’s interconnected digital ecosystem, Application Programming Interfaces (APIs) are the fundamental glue linking services, data, and users. As this reliance grows, so does the attack surface. Securing these channels is no longer optional—it’s existential. This deep dive explores the cornerstone technologies of modern API security: the authorization framework OAuth 2.0, the token format JWT, and their practical, robust implementation using Spring Security 7. Table of Contents Toggle The Pillars: OAuth2 and JWTOAuth 2.0: The Authorization Delegation FrameworkJWT: The Compact, Self-Contained Token FormatImplementing with Spring Security 7Security Best Practices & PitfallsConclusion The Pillars: OAuth2 and JWT While often mentioned together, OAuth2 and JWT serve distinct, complementary roles. Understanding this separation is crucial. OAuth 2.0: The Authorization Delegation Framework OAuth2 is not an authentication protocol nor a token format. It’s a delegation framework that allows a third-party application to obtain limited access to a user’s resources without exposing their credentials. It solves the “password anti-pattern” where apps ask for usernames and passwords for other services (e.g., “Login with Facebook”). The core components are: Resource Owner: The end-user. Client: The application requesting access (e.g., a mobile app). Authorization Server: Issues tokens after authenticating the user and obtaining consent (e.g., Keycloak, Auth0, Amazon Cognito). Resource Server: Hosts the protected resources (your API) and accepts/validates tokens. Common grant types (flows) include: Authorization Code: For server-side web applications. The most secure and common flow involves a redirect to the authorization server and a code exchange for a token. Client Credentials: For machine-to-machine (M2M) communication where a specific user context isn’t needed (e.g., service backend to backend). Resource Owner Password Credentials: Direct username/password exchange for a token. Use with extreme caution, only for trusted first-party clients. Refresh Token: Used alongside other grants to obtain new access tokens without re-prompting the user. See also Cost-Optimized Java: Right-Sizing Cloud Deployments with Performance ProfilingOAuth2 defines how to get a token, but says little about the token’s format. This is where JWT shines. JWT: The Compact, Self-Contained Token Format JSON Web Token (JWT – pronounced “jot”) is a compact, URL-safe means of representing claims between two parties. It’s a standard (RFC 7519) that defines a structure for tokens that can be digitally signed (using JWS) or encrypted (using JWE). A JWT is a string comprised of three dot-separated parts: Header: Specifies the token type (JWT) and the signing algorithm (e.g., HS256, RS256). Payload: Contains the claims—statements about an entity (typically the user) and additional metadata (e.g., sub, name, iat, exp, scope). Signature: Created by signing the encoded header and payload with a secret (or private key). This ensures the token’s integrity. The key advantage is statelessness. The resource server can validate the token’s signature and trust its claims without a call to the authorization server (database lookup), enabling excellent scalability. However, this also makes JWT revocation before expiry complex, requiring clever patterns like short-lived tokens paired with refresh tokens or using a token denylist. Together, they form a powerful duo: OAuth2 governs the secure process of obtaining permission, and JWT provides a verifiable, information-rich ticket for that permission. Implementing with Spring Security 7 Spring Security is the de facto standard for securing Spring-based applications. Version 7 brings a more declarative, resource-oriented security model and moves away from deprecated patterns. Let’s build a secure resource server. 1. Dependencies & Configuration Start with the essential dependencies (using Spring Boot 3.x / Spring Security 6.x & 7.x): <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-oauth2-resource-server</artifactId> </dependency> <!-- Alternatively, for OAuth2 Client capabilities --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-oauth2-client</artifactId> </dependency> 2. Configuring the Resource Server See also Full-Stack Java with HTMX and Thymeleaf: A Modern ApproachThe core configuration happens in a security configuration class. Spring Security 7 encourages a lambda-style DSL for cleaner configuration. import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.oauth2.jwt.JwtDecoder; import org.springframework.security.oauth2.jwt.NimbusJwtDecoder; import org.springframework.security.web.SecurityFilterChain; @Configuration @EnableWebSecurity public class SecurityConfig { @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http .authorizeHttpRequests(authz -> authz .requestMatchers("/api/public/**").permitAll() .requestMatchers("/api/admin/**").hasAuthority("SCOPE_admin") // JWT scope-based authority .requestMatchers("/api/profile/**").hasAuthority("SCOPE_profile") .anyRequest().authenticated() ) .oauth2ResourceServer(oauth2 -> oauth2 .jwt(jwt -> jwt.decoder(jwtDecoder())) // Configure JWT validation ) .sessionManagement(session -> session .sessionCreationPolicy(SessionCreationPolicy.STATELESS) // Crucial for APIs ); return http.build(); } @Bean public JwtDecoder jwtDecoder() { // Decodes & validates JWTs using the Authorization Server's public key String jwkSetUri = "https://your-auth-server/.well-known/jwks.json"; return NimbusJwtDecoder.withJwkSetUri(jwkSetUri).build(); } } This configuration: Defines URL-based authorization rules using scopes (prefixed with SCOPE_). Configures the app as an OAuth2 resource server. Sets JwtDecoder to validate token signatures using the auth server’s public keys (from a JWK Set endpoint). This is the standard for signed (RSA) JWTs. Enforces stateless sessions. 3. Handling JWT Claims in Your Application Once a JWT is validated, you can inject its claims into your controllers or service methods. import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.security.oauth2.jwt.Jwt; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class ApiController { @GetMapping("/api/profile/me") @PreAuthorize("hasAuthority('SCOPE_profile')") // Method-level security public String getUserProfile(@AuthenticationPrincipal Jwt jwt) { // Extract claims from the JWT object String userId = jwt.getSubject(); String username = (String) jwt.getClaims().get("preferred_username"); String email = (String) jwt.getClaim("email"); return String.format("User ID: %s, Username: %s, Email: %s", userId, username, email); } } 4. Going Further: Custom Token Conversion You might want to transform the Jwt object into a custom UserPrincipal for a richer domain model. @Component public class JwtToUserPrincipalConverter implements Converter<Jwt, AbstractAuthenticationToken> { @Override public AbstractAuthenticationToken convert(Jwt jwt) { // Extract roles/scopes from the 'scope' or 'roles' claim Collection<String> scopes = jwt.getClaimAsStringList("scope"); Set<SimpleGrantedAuthority> authorities = scopes.stream() .map(scope -> new SimpleGrantedAuthority("SCOPE_" + scope)) .collect(Collectors.toSet()); // Build a custom principal object UserPrincipal principal = new UserPrincipal( jwt.getSubject(), jwt.getClaim("preferred_username"), authorities ); return new JwtAuthenticationToken(jwt, authorities, principal); } } Then, register it in your configuration: .oauth2ResourceServer(oauth2 -> oauth2 .jwt(jwt -> jwt.jwtAuthenticationConverter(jwtToUserPrincipalConverter())) ) Security Best Practices & Pitfalls Always Use HTTPS: Tokens are bearer tokens. Transmit them only over TLS/SSL to prevent interception. Validate Signatures Meticulously: Never accept unsigned JWTs (alg: none). Use strong asymmetric keys (RS256) over symmetric keys (HS256) for distributed systems. Enforce Token Expiry: Always set and validate the exp claim. Use short-lived access tokens (minutes) and refresh them. Validate Audience (aud) and Issuer (iss): Ensure the token was issued for your specific API (audience) and by your trusted auth server (issuer). Avoid Storing Sensitive Data in JWT Payload: The payload is encoded, not encrypted. Assume it can be read by anyone who possesses the token. Have a Clear Revocation Strategy: For immediate revocation needs, implement a token denylist (blacklist) or use opaque tokens (reference tokens) that require introspection calls, sacrificing some performance for control. Use Scopes for Granular Permissions: Scopes (scope claim) define what the token allows (e.g., read:users, write:posts). Use them for coarse-grained authorization at the API gateway/resource server level. Use Authorities/Roles for Business Logic: Map scopes or custom claims to Spring Security GrantedAuthority objects for fine-grained authorization within your application. See also Project Valhalla Goes Mainstream: Using Value Types in ProductionConclusion API security is a multi-layered defense. OAuth2 provides a robust, standardized process for delegation and consent. JWT offers a scalable, portable vehicle for conveying authorization claims. Spring Security 7 provides an elegant, powerful toolkit to implement these standards seamlessly in your Java applications. By deeply understanding the theory of these protocols and applying the practical patterns shown here—leveraging resource server configuration, stateless sessions, proper signature validation, and claim-based authorization—you can build APIs that are not only functional but fundamentally secure, scalable, and ready for the demands of modern software architecture. Remember, security is not a feature you add but a foundation you build upon. Java