What is the difference between maxSessionsPreventsLogin(true) and (false)?
answer
- false = newest wins, oldest expired
- true = oldest wins, new login rejected
- true throws SessionAuthenticationException
- true + stale registry = lockout risk
- limit is per-principal (equals/hashCode)
basics
~20 sWith false (default) a new login is allowed and the oldest session is expired. With true the new login is refused (throws SessionAuthenticationException) and existing sessions stay alive — old sessions win over new ones.
solid answer
~40 s`maxSessionsPreventsLogin` decides who loses when a user exceeds `maximumSessions`. Default `false`: the new authentication succeeds and Spring's `ConcurrentSessionControlAuthenticationStrategy` marks the oldest session's `SessionInformation` as expired; that session is torn down by `ConcurrentSessionFilter` on its next request ("kick the old device"). With `true`: the strategy throws `SessionAuthenticationException` at login time, so the new attempt is rejected and current sessions are untouched ("first-in wins"). Choose `true` for strict single-seat licensing where you never want an active user silently kicked; choose `false` for consumer UX where the latest device should just take over. A caveat with `true`: if a previous session is still counted (e.g. the registry wasn't told it was destroyed), a legitimate user can be locked out — which is why correct `HttpSessionEventPublisher` wiring matters.
code
java · 9 lineshttp.sessionManagement(session -> session
.maximumSessions(1)
.maxSessionsPreventsLogin(true) // reject the new login instead of
// expiring the existing session
.expiredUrl("/login?expired"));
// With true, a second concurrent login triggers:
// org.springframework.security.web.authentication.session
// .SessionAuthenticationException: "Maximum sessions of 1 ... exceeded"go deeper
Know false = new login wins, true = new login blocked.
Explain the mechanism: expireNow vs SessionAuthenticationException, and the lazy expiry on next request.
Tie the true-mode lockout risk to HttpSessionEventPublisher and registry accuracy; discuss per-principal equals/hashCode.
Weigh UX and security trade-offs, and note both modes depend on a reliable, ideally shared, SessionRegistry in a cluster.
**The setting.** `maxSessionsPreventsLogin` is a boolean on `sessionManagement().maximumSessions(n)`. It only matters when a login would push a principal *over* the `maximumSessions` limit. **`false` — the default (newest wins).** - The login is allowed to proceed. - `ConcurrentSessionControlAuthenticationStrategy` finds the principal's least-recently-used session in the `SessionRegistry` and calls `SessionInformation.expireNow()`, flipping its `expired` flag to true. - The old session is *not* immediately invalidated server-side; it dies lazily. On that old session's **next request**, `ConcurrentSessionFilter` sees `sessionInformation.isExpired()`, invalidates the `HttpSession`, runs the logout handlers, and redirects to the configured `expiredUrl` (or writes a default expired message). - User-visible effect: logging in on a new device silently boots the oldest device. **`true` (oldest wins / login prevented).** - At login, `ConcurrentSessionControlAuthenticationStrategy` throws `SessionAuthenticationException` ("Maximum sessions of N for this principal exceeded"). - The new authentication fails; existing sessions are untouched. - User-visible effect: the second device sees a login error until an existing session logs out or times out. **Why the wiring matters more with `true`.** The decision is based on the *count in the registry*. If a session ended (browser closed, timeout, logout) but the registry was never notified, its stale entry still counts toward the limit. With `true` that stale count can lock a legitimate user out entirely. The fix is registering `HttpSessionEventPublisher` so `HttpSessionDestroyedEvent` removes ended sessions from `SessionRegistryImpl`. With `false` a stale entry is less harmful because the old (possibly dead) session just gets expired. **Edge cases.** - The limit is per-principal, keyed by the principal's `equals`/`hashCode`. If your custom principal object doesn't implement these correctly, counting breaks. - With `true`, there is no "queue": it's a hard rejection, not a wait. - Setting `maximumSessions` at all activates the concurrency filter/strategies even if you leave `maxSessionsPreventsLogin` at default. **When to use which.** `true`: strict license seats, security-sensitive apps that must never silently drop an authenticated session, admin consoles. `false`: consumer apps where 'log in on my new phone and it just works' is the expected UX.
- With maxSessionsPreventsLogin(true), how can a legitimate user get wrongly locked out, and how do you prevent it?If a prior session ended but the SessionRegistry still counts it (no destroy event), the count stays at the cap. Registering HttpSessionEventPublisher so HttpSessionDestroyedEvent purges the registry prevents this.
- Is the old session invalidated immediately when maxSessionsPreventsLogin is false?No. It is only flagged expired in the registry; ConcurrentSessionFilter invalidates it lazily on that session's next request.
saying these in an interview costs you the question
- Saying true expires the old session (it actually blocks the new login)
- Claiming the old session is killed instantly rather than on its next request
- Not knowing which is the default (false is default)