If you've worked with web apps, you've probably run into the term "session". A session is a mechanism that lets the server "remember" a user across requests. This is critical for authentication — otherwise you'd have to type your login and password on every click.
How does a session work? When a user authenticates, the server creates a unique identifier (Session ID). That identifier is sent to the client (for example, the browser), usually as a cookie. On each subsequent request the client attaches that identifier, which lets the server know who the user is.
Session management in Spring Security
Spring Security has powerful features for handling sessions. Let's look at how to configure and manage them.
Session policy configuration
One of the first tasks you'll meet is configuring the session management policy. For example, you can decide how many active sessions a single user can have. For that you can use the sessionManagement() and maximumSessions() methods.
Example:
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.sessionManagement()
.maximumSessions(1) // Only one session per user
.maxSessionsPreventsLogin(true); // Prevent login when limit is exceeded
}
}
Here we limited a user to one active session. If they try to log in from another device, they'll be denied.
Handling session termination
Spring Security lets you configure what happens when a session ends (for example, if the session expires or is terminated manually). You can use the SessionRegistry interface for that.
A logout handler might look like this:
@Component
public class CustomLogoutHandler implements LogoutHandler {
@Override
public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
System.out.println("Session ended: " + authentication.getName());
}
}
Session storage
Sessions can be stored either in server memory or in a database. Using a database is common in distributed systems where a load balancer might route a user to different servers between requests. In such cases sessions need to be "shared".
Security tokens: why sessions aren't enough?
In the era of microservices and REST API, tokens take the spotlight. Tokens are compact strings that represent some information (for example, a user identifier). Unlike sessions, tokens don't require server-side storage.
Advantages of tokens
- Server-state independence: tokens are stored on the client, which makes scaling apps easier.
- Reduced load: the server doesn't need to keep state for every user.
- API friendliness: tokens can be sent in an HTTP request header, which is ideal for REST APIs.
JWT (JSON Web Token)
JWT is a popular token format that is basically an encoded JSON string. Each token consists of three parts:
- Header (header): the token type and the encryption algorithm used.
- Payload (payload): the token data, e.g.
userIdorrole. - Signature (signature): protects the token from tampering.
Example JWT token:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VySWQiLCJyb2xlIjoiVVNFUiJ9.abc123signature
Using JWT in Spring Security
Let's add JWT to our app's security.
To generate tokens you'll need a library, for example io.jsonwebtoken (JWT). Here's an example of creating a token:
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import java.util.Date;
public class JwtTokenUtil {
private static final String SECRET_KEY = "mySecretKey";
public String generateToken(String username) {
return Jwts.builder()
.setSubject(username)
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + 86400000)) // 1 day
.signWith(SignatureAlgorithm.HS256, SECRET_KEY)
.compact();
}
}
Validating tokens
Before using a token, make sure it's genuine and not expired. You need to decode it and verify its signature:
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.Claims;
public class JwtTokenUtil {
private static final String SECRET_KEY = "mySecretKey";
public Claims validateToken(String token) {
return Jwts.parser()
.setSigningKey(SECRET_KEY)
.parseClaimsJws(token)
.getBody();
}
}
The validateToken() method returns the token's payload if it's valid.
Setting up JWT authentication
Now let's integrate JWT into Spring Security. We'll create a filter for that:
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
@Autowired
private JwtTokenUtil jwtTokenUtil;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
String token = request.getHeader("Authorization");
if (token != null && token.startsWith("Bearer ")) {
token = token.substring(7); // Remove "Bearer "
Claims claims = jwtTokenUtil.validateToken(token);
if (claims != null) {
String username = claims.getSubject();
// Create authentication object
UsernamePasswordAuthenticationToken auth =
new UsernamePasswordAuthenticationToken(username, null, Collections.emptyList());
SecurityContextHolder.getContext().setAuthentication(auth);
}
}
filterChain.doFilter(request, response);
}
}
The filter checks the token before the request goes further.
Example of using tokens in our app
We created a REST API for authentication. The /login controller returns a JWT token. The client should send this token in the Authorization header on subsequent requests.
Controller:
@RestController
public class AuthController {
@Autowired
private JwtTokenUtil jwtTokenUtil;
@PostMapping("/login")
public ResponseEntity<?> login(@RequestParam String username, @RequestParam String password) {
// Here you would typically check the username/password
String token = jwtTokenUtil.generateToken(username);
return ResponseEntity.ok(new HashMap<String, String>() {{
put("token", token);
}});
}
}
Now your client can make an authorized request:
curl -H "Authorization: Bearer <your_token>" http://localhost:8080/protected/resource
When to use sessions and when to use tokens?
If you're working with traditional web applications, sessions are still a great choice. They're simple, convenient, and well integrated into the Spring ecosystem.
However, for REST APIs and microservices sessions are less convenient. Tokens, especially JWT, take the lead here — they offer mobility, easier scaling, and server-state independence.
GO TO FULL VERSION