๐ด The Error You're Seeing
Confirm this matches your console output. If it does, you're in the right place.
org.springframework.security.web.authentication.session.SessionAuthenticationException: This session has been expired (possibly due to multiple concurrent logins being attempted as the same user)
at org.springframework.security.web.authentication.session.ConcurrentSessionControlAuthenticationStrategy.onAuthentication(ConcurrentSessionControlAuthenticationStrategy.java:156)โก Quick Fix Works 80% of the time
Increase the maximumSessions in your SessionManagement configuration, or set maxSessionsPreventsLogin to true.
http.sessionManagement(session -> session
.maximumSessions(10)
.maxSessionsPreventsLogin(false)
);๐ง Why this Happens
Tap to expand the deep technical explanation
You configured Spring Security to limit the number of concurrent active sessions a user can have (e.g., `maximumSessions(1)`). The user logged in from Chrome, and then logged in again from Firefox. Spring Security detected the second login and expired the first session, throwing this exception when the first browser tries to make a new request.
The HITEC City Parking Spot Analogy:
It's like a gym that only allows one active membership card per person. If you get a new card, the old card is immediately deactivated and snapped in half the next time you try to swipe it.
๐ How to Reproduce Confirm this is your error
Configure `http.sessionManagement(s -> s.maximumSessions(1))`. Log in as 'admin' in Chrome. Log in as 'admin' in Firefox. Go back to Chrome and click any link. The Chrome session will crash with this error.
๐ ๏ธ Solutions (5 Ways to Fix)
Increase maximumSessions limit
๐ Use this if you want to allow users to be logged in from multiple devices.
Set the limit to a higher number (or -1 for unlimited) to prevent the session from being invalidated.
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.sessionManagement(session -> session
.maximumSessions(3) // Allow 3 concurrent devices
);
return http.build();
}Prevent the second login instead
๐ Use this if you strictly want to block users from sharing accounts.
Set `maxSessionsPreventsLogin(true)`. The second login attempt will be rejected, and the first session remains active.
http.sessionManagement(session -> session
.maximumSessions(1)
.maxSessionsPreventsLogin(true) // Block second login
);Register SessionRegistry bean
๐ Use this if your session management config is being ignored.
Spring Security needs a SessionRegistry to track active sessions. If it's missing, concurrent login control silently fails or behaves erratically.
@Bean
public SessionRegistry sessionRegistry() {
return new SessionRegistryImpl();
}
// Inject it into your config:
http.sessionManagement(session -> session
.maximumSessions(1)
.sessionRegistry(sessionRegistry())
);Add HttpSessionEventPublisher
๐ Use this if expired sessions aren't being cleaned up from the registry.
Spring needs to know when a session dies to remove it from the active list. This listener does exactly that.
@Bean
public HttpSessionEventPublisher httpSessionEventPublisher() {
return new HttpSessionEventPublisher();
}Handle the exception in UI/API
๐ Use this to show a clean 'Session Expired' page instead of a 500 error.
Configure an InvalidSessionHandler to redirect the user to the login page with a message.
http.sessionManagement(session -> session
.invalidSessionUrl("/login?expired=true")
);๐ Version Notes
Session management configured via WebSecurityConfigurerAdapter.
Configured via SecurityFilterChain lambda DSL.
๐ก๏ธ How to Prevent This Next Time
If using stateless JWTs, you don't need session management at all. Set `SessionCreationPolicy.STATELESS` to avoid this entire class of errors.