Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 71

Stateless Spring Security with JWT

Recommended resources

Hello again! In our last lesson, we successfully implemented a custom JwtAuthenticationFilter to validate tokens on incoming requests. As part of that setup, we added a line to our SecurityConfig to set the session management policy to STATELESS, but we didn't explore its full implications.

This lesson focuses squarely on that concept. We'll explore what "stateless authentication" truly means, why it's the standard for modern APIs using JWT, and how to correctly configure Spring Security to enforce it. Your experience with building REST APIs in the MERN stack will be a great asset here, as the principle of statelessness is universal, even though the implementation details differ.

By the end of this session, you will not only understand the theory behind statelessness but also see a more declarative, production-oriented way to configure it in Spring, moving beyond our initial custom filter implementation.

From Stateful Sessions to Stateless Tokens

Traditionally, web applications managed user authentication using server-side sessions. The flow looked something like this:

  1. A user logs in.
  2. The server creates a session in its memory, associating it with the user's details.
  3. The server sends a unique session ID back to the user's browser, usually stored in a cookie.
  4. On every subsequent request, the browser sends the session ID, and the server uses it to look up the user's session data.

This is stateful because the server must maintain the "state" (the session data) for every active user.

Stateless authentication, which is the cornerstone of JWT, flips this model. The server stores no session data. Instead, the client holds a self-contained token (the JWT) that includes all the necessary information for authentication.

This diagram illustrates the stateless flow. The server validates the JWT on each request without needing to store any prior session information. Each request is independent and self-contained.

The primary advantages of this stateless approach are scalability and decoupling. Since no state is stored on the server, you can easily add more server instances to handle trafficβ€”any server can validate any token. This is fundamental to building scalable microservices architectures.


Enforcing Statelessness in Spring Security

In our previous lesson, we added two crucial lines to our SecurityConfig to begin enforcing this stateless behavior. Let's examine them now.

1. Disabling Session Creation

In your SecurityFilterChain bean, you have this configuration:

.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))

This line is the most direct way to tell Spring Security how to handle sessions. SessionCreationPolicy.STATELESS instructs the framework to never create an HttpSession and to never use one to retrieve the SecurityContext. This ensures our application doesn't fall back on session-based mechanisms and relies solely on the token provided in each request.

The following video from Amigoscode provides a concise visual and code-based explanation for this configuration.

Spring Security Architecture Explained

This video discusses Spring Security architecture and configurations. The segment highlighted below focuses specifically on disabling sessions for a stateless, JWT-based setup.

Watch the part of the video from this timestamp, where the presenter points to the .sessionManagement() configuration and explains its role in making the application stateless.

2. Disabling CSRF Protection

You also have this line in your configuration:

.csrf(csrf -> csrf.disable())

Cross-Site Request Forgery (CSRF) is an attack where a malicious website tricks a user into submitting an unwanted action to a site where they are authenticated. Standard CSRF protection in frameworks like Spring works by using sessions. A unique, unpredictable token is stored in the user's session and also embedded in web forms. When a form is submitted, the server checks if the submitted token matches the one in the session.

Since our API is stateless and doesn't use sessions, this mechanism is incompatible. Furthermore, because we are using JWTs sent in the Authorization header (and not in cookies that are automatically sent by the browser), our API is not vulnerable to traditional CSRF attacks. Therefore, disabling it is both necessary and safe for our use case.

The article "Secure Your Spring API With JWT and MongoDB" explains this point clearly.

Secure Your Spring API With JWT and MongoDB

This article provides an alternative way to set up JWT security. The section on security configuration offers a great explanation for disabling CSRF and enforcing a stateless session policy.

In the Security configuration section, read the paragraph that starts with this sentence. It perfectly summarizes why this is a standard practice for stateless, token-based APIs.


An Alternative: The oauth2ResourceServer Approach

Our current implementation using a custom JwtAuthenticationFilter is a fantastic way to learn the mechanics of JWT validation from the ground up. However, Spring Security offers a more declarative and streamlined approach through its OAuth2 Resource Server module. This method lets the framework handle the boilerplate of token extraction and validation, making your security configuration cleaner and less error-prone.

Let's refactor our application to use this approach. This will also be a great opportunity to upgrade our token security from a shared secret (HS256) to public/private key cryptography (RS256).

Step 1: Add the OAuth2 Resource Server Dependency

First, add the necessary dependency to your pom.xml file. This provides all the classes we'll need.

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>

After adding this, be sure to reload your Maven project.

Step 2: Generate RSA Keys

Asymmetric encryption (RS256) is more secure for many use cases because the key used to sign tokens (private key) is kept secret on the server, while the key used to verify them (public key) can be shared.

Let's generate these keys using OpenSSL. The following article provides the exact commands.

Secure Your Spring API With JWT and MongoDB

This article details the process of generating and configuring RSA keys for use with Spring Security.

Follow the instructions in the section "Creating RSA keys". You will need to run three openssl commands in a new src/main/resources/certs directory to generate the private.pem and public.pem files.

Step 3: Configure Key Properties

Now, we need to make these keys available to our Spring application.

  1. Create a new Java record named RsaKeyProperties in your config package. This class will bind the key locations from application.properties to Java objects.
  2. Enable this configuration in your main application class using @EnableConfigurationProperties.
  3. Add the key locations to your application.properties file.

The article you just read provides the complete code for this.

Secure Your Spring API With JWT and MongoDB

This reading will guide you through setting up the configuration properties for the RSA keys.

Refer to the same section, "Creating RSA keys," and follow the instructions starting from "Create a class RsaKeyProperties" to the end of the section. This will show you the RsaKeyProperties record, the @EnableConfigurationProperties annotation, and the lines to add to application.properties.

Step 4: Refactor SecurityConfig and TokenService

This is the main part of our refactoring. We will modify SecurityConfig to use the oauth2ResourceServer and create beans for encoding/decoding JWTs.

  1. Modify SecurityConfig.java:

    • Inject your RsaKeyProperties record.
    • Remove the injection of JwtAuthenticationFilter and the .addFilterBefore(...) call.
    • Replace .anyRequest().authenticated() with a chain that includes .oauth2ResourceServer().
    • Add two new beans: JwtDecoder (which uses the public key) and JwtEncoder (which uses both keys).
  2. Modify Your Token Generation Logic:

    • Your existing JwtService likely uses a different library or approach (e.g., HS256). We will adapt it to use the new JwtEncoder bean. The article provides a great example of a TokenService that does exactly this.

The following two resources provide the exact code snippets needed for this refactoring.

Secure Your Spring API With JWT and MongoDB

This article provides the full implementation details for the oauth2ResourceServer approach.

In the Security configuration section, find the filterChain bean definition. Update yours to match the structure using this code. Notice how it uses .oauth2ResourceServer() and no longer has .addFilterBefore(). In the Encoding and decoding JWT section, copy the constructor injection for RsaKeyProperties and the JwtDecoder and JwtEncoder beans into your SecurityConfig. In the Creating tokens section, adapt or replace your existing JwtService with the TokenService implementation provided. It uses the JwtEncoder to generate the token. Notice how it builds a JwtClaimsSet.

After completing this refactoring, restart your application and test it with Postman. The user-facing flow should be identical: log in to get a token, and use that token in the Authorization: Bearer <token> header to access protected endpoints. Internally, however, your application is now using a more robust and idiomatic Spring Security implementation.


Conclusion

In this lesson, you've taken a deep dive into what it means to build a stateless authentication system. You now understand not only the "how" but also the "why" behind key configurations like setting SessionCreationPolicy.STATELESS and disabling CSRF.

Key Takeaways:

  • Stateless authentication means the server stores no session data; each request must contain all information needed for authentication, typically in a JWT.
  • This approach is critical for scalability and is a core principle of microservices architecture.
  • In Spring Security, you enforce statelessness with .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)).
  • CSRF protection should be disabled for stateless, token-based APIs.
  • Spring's oauth2ResourceServer module offers a declarative, production-ready alternative to writing a custom JWT filter, especially when combined with RS256 key pairs.

Now that we have a robust, stateless authentication mechanism in place, our next step is to use the information embedded in the token to make finer-grained authorization decisions. In the next lesson, we will learn how to secure individual service methods based on user roles using the @PreAuthorize annotation.