This is an automated email from the ASF dual-hosted git repository. lprimak pushed a commit to branch 3.x in repository https://gitbox.apache.org/repos/asf/shiro.git
commit 8a5c0d29a0996dbdeb2ff114c8725a8b2ed1ce0d Author: lprimak <[email protected]> AuthorDate: Wed May 20 22:03:10 2026 -0500 bugfix: session attributes survive id rotation in native session mode fixes #2710 --- .../apache/shiro/mgt/DefaultSecurityManager.java | 14 +++++++++- .../shiro/subject/DelegatingSubjectTest.java | 30 ++++++++++++++++++++++ 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/org/apache/shiro/mgt/DefaultSecurityManager.java b/core/src/main/java/org/apache/shiro/mgt/DefaultSecurityManager.java index d97be6894..1a3ba503c 100644 --- a/core/src/main/java/org/apache/shiro/mgt/DefaultSecurityManager.java +++ b/core/src/main/java/org/apache/shiro/mgt/DefaultSecurityManager.java @@ -42,6 +42,8 @@ import org.slf4j.LoggerFactory; import java.io.Serializable; import java.util.Collection; +import java.util.HashMap; +import java.util.Map; /** * The Shiro framework's default concrete implementation of the {@link SecurityManager} interface, @@ -303,7 +305,17 @@ public class DefaultSecurityManager extends SessionsSecurityManager { * @param subject Subject */ protected void beforeSuccessfulLogin(Subject subject) { - stopSession(subject); + Session session = subject.getSession(false); + if (session != null) { + Map<Object, Object> attributes = new HashMap<>(); + session.getAttributeKeys().forEach(key -> attributes.put(key, session.getAttribute(key))); + stopSession(subject); + var newSession = subject.getSession(); + var keys = newSession.getAttributeKeys(); + attributes.entrySet().stream() + .filter(entry -> !keys.contains(entry.getKey())) + .forEach(entry -> newSession.setAttribute(entry.getKey(), entry.getValue())); + } } protected void onSuccessfulLogin(AuthenticationToken token, AuthenticationInfo info, Subject subject) { diff --git a/core/src/test/java/org/apache/shiro/subject/DelegatingSubjectTest.java b/core/src/test/java/org/apache/shiro/subject/DelegatingSubjectTest.java index ecccb0db5..9b2a2175a 100644 --- a/core/src/test/java/org/apache/shiro/subject/DelegatingSubjectTest.java +++ b/core/src/test/java/org/apache/shiro/subject/DelegatingSubjectTest.java @@ -220,6 +220,36 @@ public class DelegatingSubjectTest { LifecycleUtils.destroy(sm); } + @Test + void sessionAttributesSurviveLoginSessionRotation() { + Ini ini = new Ini(); + Ini.Section users = ini.addSection("users"); + users.put("user1", "user1,role1"); + users.put("user2", "user2,role2"); + users.put("user3", "user3,role3"); + SecurityManager sm = new BasicIniEnvironment(ini).getSecurityManager(); + Subject subject = new Subject.Builder(sm).buildSubject(); + + subject.login(new UsernamePasswordToken("user1", "user1")); + subject.logout(); + + Session preLoginSession = subject.getSession(true); + preLoginSession.setAttribute("tenantId", "ACME"); + Serializable preLoginSessionId = preLoginSession.getId(); + + subject.login(new UsernamePasswordToken("user1", "user1")); + assertThat(subject.isAuthenticated()).isTrue(); + + Session postLoginSession = subject.getSession(false); + assertThat(postLoginSession).isNotNull(); + + assertThat(preLoginSessionId).as("session ID should change on login (session fixation protection)") + .isNotEqualTo(postLoginSession.getId()); + assertThat(postLoginSession.getAttribute("tenantId")) + .as("session attributes set before login must survive session rotation") + .isEqualTo("ACME"); + } + @Test void testToString() { // given
