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

Reply via email to