Spring Security Session Management: Concurrent Logins & Fixation (2025)
Session management is boring. I get it. You want to write features, not worry about cookie flags.
But here’s the reality: if you screw this up, your users get hijacked. At www.codegigs.app, I review student code where simple misconfigurations allow session fixation attacks or prevent users from logging in on multiple devices. It’s messy.
If you aren’t using JWTs (which are stateless), you are using Sessions. Here is how to configure them so they don’t bite you.
Preventing Session Fixation (The Silent Killer)
Session Fixation is where an attacker sets a user’s session ID before they log in. If that ID doesn’t change after login, the attacker still has access. Spring Security handles this by default, but you need to know how it works so you don’t accidentally disable it.
The strategy you want is changeSessionId() (for Servlet 3.1+) or newSession().
// SecurityConfig.java
// Spring Boot 3.2+
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.sessionManagement(session -> session
// Migrate session attributes but change the ID (Best Practice)
.sessionFixation(SessionFixationConfigurer::migrateSession)
);
return http.build();
}
By default, Spring Boot uses changeSessionId. This is usually fine. But if you have sensitive data in the anonymous session, force a newSession() to wipe the slate clean.
Concurrent Sessions: Blocking Double Logins
This is the feature everyone asks for: “How do I stop users from sharing accounts?”
You want to limit a user to 1 active session. If they log in on a phone, their laptop session should expire (or be blocked). Configuring this requires two beans, not just one.
// SecurityConfig.java
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.sessionManagement(session -> session
.maximumSessions(1)
// true = block second login
// false = kick out first login (default)
.maxSessionsPreventsLogin(false)
.sessionRegistry(sessionRegistry())
);
return http.build();
}
// CRITICAL: Without this bean, session limiting simply WON'T WORK
// Spring needs to hear session lifecycle events to clean up the registry
@Bean
public HttpSessionEventPublisher httpSessionEventPublisher() {
return new HttpSessionEventPublisher();
}
@Bean
public SessionRegistry sessionRegistry() {
return new SessionRegistryImpl();
}
The “Gotcha”: See that HttpSessionEventPublisher? I spent three hours debugging a “memory leak” once because I forgot it. Without it, Spring doesn’t know when sessions expire naturally (like closing a browser), so the SessionRegistry thinks the user is still logged in forever.
Stateless vs Stateful (APIs vs MVC)
If you are building a REST API with JWTs, you do not want sessions. Spring Security creates sessions by default. You need to kill that behavior.
We cover this extensively in our Core Security Architecture lesson, but here is the snippet:
// For JWT APIs
http.sessionManagement(sess -> sess
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
);
Warning: “Stateless” means Spring won’t save the SecurityContext. If you don’t send the token in every single request, the user is anonymous. No exceptions.
Production Config: Cookies & Timeouts
Don’t rely on Tomcat defaults. Set these in your application.yml to harden your app against XSS and sniffing.
server:
servlet:
session:
timeout: 15m # Kill idle sessions fast
cookie:
http-only: true # JS can't read the cookie (Blocks XSS)
secure: true # Only send over HTTPS
name: __HOST-SESSION # Prefix for extra security rules
If you are running on localhost without HTTPS, secure: true will prevent you from logging in. Use profiles to separate Dev vs Prod config.
Handling Logout Properly
Logging out isn’t just redirecting to the home page. You need to nuke the session and the cookie.
http.logout(logout -> logout
.logoutUrl("/auth/logout")
.addLogoutHandler(new SecurityContextLogoutHandler()) // Clean up Context
.invalidateHttpSession(true) // Kill the session
.deleteCookies("JSESSIONID", "REMEMBER_ME") // Kill the cookies
.logoutSuccessHandler((req, res, auth) -> res.setStatus(200)) // Don't redirect for APIs
);
Building a SaaS Platform?
Session management is just step one. You need Rate Limiting, 2FA, and Audit Logs.
We build a complete production-grade security stack in the Master Class.
Summary
Session management shouldn’t be an afterthought.
- Use
HttpSessionEventPublisherif you limit concurrent sessions. - Use
SessionCreationPolicy.STATELESSfor JWT APIs. - Always set
http-only: trueon cookies.
Now that your sessions are secure, it’s time to look at how to handle failures gracefully. Check out our guide on Custom Authentication Failure Handlers.