Skip to content

Fix SessionRegistryImpl losing track of session when ChangeSessionIdAuthenticationStrategy runs without HttpSessionEventPublisher#19214

Open
klouds27 wants to merge 2 commits into
spring-projects:mainfrom
klouds27:fix/session-registry-fixation-without-event-publisher
Open

Fix SessionRegistryImpl losing track of session when ChangeSessionIdAuthenticationStrategy runs without HttpSessionEventPublisher#19214
klouds27 wants to merge 2 commits into
spring-projects:mainfrom
klouds27:fix/session-registry-fixation-without-event-publisher

Conversation

@klouds27
Copy link
Copy Markdown

Problem

When concurrency control is enabled and ChangeSessionIdAuthenticationStrategy rotates the session ID during authentication, SessionRegistryImpl only detects the change if HttpSessionEventPublisher is registered as a servlet listener. Without it, HttpSessionIdChangedEvent is never published, so the registry retains the old session ID as a ghost entry.

On the next authentication, ConcurrentSessionControlAuthenticationStrategy counts one extra session per login and incorrectly expires the valid session, breaking concurrency control entirely.

Root cause

AbstractSessionFixationProtectionStrategy.onSessionChange() publishes SessionFixationProtectionEvent, which extends AbstractAuthenticationEvent. SessionRegistryImpl only listens for AbstractSessionEvent, so it never sees the ID change.

SessionRegistryImpl already handles SessionIdChangedEvent (an AbstractSessionEvent subtype) correctly — it removes the old entry and re-registers under the new ID — but that event is only published by HttpSessionEventPublisher via the servlet container's HttpSessionIdListener callback.

Fix

AbstractSessionFixationProtectionStrategy.onSessionChange() now also publishes a SessionFixationProtectionSessionIdChangedEvent (a new package-private class extending SessionIdChangedEvent). SessionRegistryImpl handles SessionIdChangedEvent instances already, so it correctly migrates the entry on every session fixation rotation without any additional configuration.

HttpSessionEventPublisher is still required to receive session-destroyed notifications for expired sessions.

Tests

  • ChangeSessionIdAuthenticationStrategyTests#onAuthenticationPublishesSessionIdChangedEventWithoutHttpSessionEventPublisher — verifies both SessionFixationProtectionEvent and SessionIdChangedEvent are published
  • SessionRegistryImplTests#sessionIdChangedEventWhenOldSessionRegisteredThenMigratesSessionWithoutHttpSessionEventPublisher — verifies the registry migrates correctly and the principal ends up with exactly one session
  • Updated two existing tests in DefaultSessionAuthenticationStrategyTests to assert both published events

Fixes gh-19007

Copilot AI review requested due to automatic review settings May 25, 2026 10:58
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

Adds publication of a SessionIdChangedEvent during session fixation protection so SessionRegistryImpl can track session ID migrations without requiring HttpSessionEventPublisher.

Changes:

  • Publish an additional SessionIdChangedEvent alongside SessionFixationProtectionEvent during session fixation protection.
  • Add a dedicated internal event type to carry old/new session IDs from the fixation strategy.
  • Update/add tests and javadocs to reflect the new event behavior and SessionRegistryImpl support.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
web/src/main/java/org/springframework/security/web/authentication/session/AbstractSessionFixationProtectionStrategy.java Publishes an additional session-id-changed event on session change.
web/src/main/java/org/springframework/security/web/authentication/session/SessionFixationProtectionSessionIdChangedEvent.java New internal SessionIdChangedEvent subclass carrying stable old/new IDs.
web/src/test/java/org/springframework/security/web/session/DefaultSessionAuthenticationStrategyTests.java Updates assertions to expect two published events and validate both.
web/src/test/java/org/springframework/security/web/authentication/session/ChangeSessionIdAuthenticationStrategyTests.java Adds coverage for publishing SessionIdChangedEvent when an app event publisher is set.
core/src/main/java/org/springframework/security/core/session/SessionRegistryImpl.java Updates class javadoc to mention SessionIdChangedEvent support and fixation tracking.
core/src/test/java/org/springframework/security/core/session/SessionRegistryImplTests.java Adds test ensuring SessionIdChangedEvent migrates the registered session.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 131 to 136
protected void onSessionChange(String originalSessionId, HttpSession newSession, Authentication auth) {
this.applicationEventPublisher
.publishEvent(new SessionFixationProtectionEvent(auth, originalSessionId, newSession.getId()));
this.applicationEventPublisher
.publishEvent(new SessionFixationProtectionSessionIdChangedEvent(newSession, originalSessionId));
}
Comment on lines +61 to +63
assertThat(events.get(0)).isInstanceOf(SessionFixationProtectionEvent.class);
assertThat(events.get(1)).isInstanceOf(SessionIdChangedEvent.class);
SessionIdChangedEvent idChangedEvent = (SessionIdChangedEvent) events.get(1);
}

@Test
public void onAuthenticationPublishesSessionIdChangedEventWithoutHttpSessionEventPublisher() {
@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged label May 25, 2026
@klouds27 klouds27 force-pushed the fix/session-registry-fixation-without-event-publisher branch from 862267d to 274d5d3 Compare May 25, 2026 20:19
klouds27 added 2 commits May 25, 2026 22:21
…essionEventPublisher

When ChangeSessionIdAuthenticationStrategy rotates the session ID during
authentication, SessionRegistryImpl only noticed the change if
HttpSessionEventPublisher was registered as a servlet listener to publish
HttpSessionIdChangedEvent. Without it, the old session ID remained in the
registry as a ghost entry, causing ConcurrentSessionControlAuthenticationStrategy
to count one extra session per login and incorrectly expire valid sessions.

Fix: AbstractSessionFixationProtectionStrategy.onSessionChange() now also
publishes a SessionFixationProtectionSessionIdChangedEvent (extending
SessionIdChangedEvent), which SessionRegistryImpl already handles. This removes
the dependency on HttpSessionEventPublisher for session-fixation-induced ID changes;
HttpSessionEventPublisher is still required for expiry notifications.

Closes spring-projectsgh-19007

Signed-off-by: admin <adalwolfad@gmail.com>
Signed-off-by: klouds27 <adalwolf@gmail.com>
Signed-off-by: admin <adalwolfad@gmail.com>
Signed-off-by: klouds27 <adalwolf@gmail.com>
@klouds27 klouds27 force-pushed the fix/session-registry-fixation-without-event-publisher branch 2 times, most recently from 830fed3 to 9545081 Compare May 25, 2026 23:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

status: waiting-for-triage An issue we've not yet triaged

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Concurrency Control Broken when HttpSession.changeSessionId then SessionRegistry.registerNewSession

3 participants