This is an automated email from the ASF dual-hosted git repository.

lprimak pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/shiro.git

commit 4bfa8ad122aca824234bc7ba55ba25d20c394165
Author: lprimak <[email protected]>
AuthorDate: Wed Oct 21 14:22:23 2026 -0500

    bugfix: prevent usage of ThreadState with ScopedValues in JDK 25 or later
---
 .../apache/shiro/subject/support/SubjectCallable.java   |  4 ++++
 .../apache/shiro/subject/support/SubjectRunnable.java   |  4 ++++
 .../shiro/subject/support/SubjectThreadState.java       | 10 +++++++++-
 .../apache/shiro/mgt/AbstractSecurityManagerTest.java   |  7 ++-----
 .../java/org/apache/shiro/test/AbstractShiroTest.java   | 17 ++++-------------
 .../AbstractAuthorizationAnnotationTest.java            | 12 ++++--------
 6 files changed, 27 insertions(+), 27 deletions(-)

diff --git 
a/core/src/main/java/org/apache/shiro/subject/support/SubjectCallable.java 
b/core/src/main/java/org/apache/shiro/subject/support/SubjectCallable.java
index 2b0fee6f7..c5e347cee 100644
--- a/core/src/main/java/org/apache/shiro/subject/support/SubjectCallable.java
+++ b/core/src/main/java/org/apache/shiro/subject/support/SubjectCallable.java
@@ -72,6 +72,10 @@ public class SubjectCallable<V> implements Callable<V> {
     }
 
     protected SubjectCallable(Subject subject, ThreadState threadState, 
Callable<V> delegate) {
+        if (threadState != null && ScopedValues.INSTANCE.isSupported()) {
+            throw new IllegalStateException("ThreadState is not supported when 
ScopedValues are in use "
+                    + "(JDK 25 or later), as it would be silently ignored.");
+        }
         if (threadState == null && !ScopedValues.INSTANCE.isSupported()) {
             throw new IllegalArgumentException("ThreadState argument cannot be 
null.");
         }
diff --git 
a/core/src/main/java/org/apache/shiro/subject/support/SubjectRunnable.java 
b/core/src/main/java/org/apache/shiro/subject/support/SubjectRunnable.java
index 34d336ba2..21319a67f 100644
--- a/core/src/main/java/org/apache/shiro/subject/support/SubjectRunnable.java
+++ b/core/src/main/java/org/apache/shiro/subject/support/SubjectRunnable.java
@@ -84,6 +84,10 @@ public class SubjectRunnable implements Runnable {
      * @throws IllegalArgumentException if either the {@code ThreadState} or 
{@link Runnable} arguments are {@code null}.
      */
     protected SubjectRunnable(Subject subject, ThreadState threadState, 
Runnable delegate) throws IllegalArgumentException {
+        if (threadState != null && ScopedValues.INSTANCE.isSupported()) {
+            throw new IllegalStateException("ThreadState is not supported when 
ScopedValues are in use "
+                    + "(JDK 25 or later), as it would be silently ignored.");
+        }
         if (threadState == null && !ScopedValues.INSTANCE.isSupported()) {
             throw new IllegalArgumentException("ThreadState argument cannot be 
null.");
         }
diff --git 
a/core/src/main/java/org/apache/shiro/subject/support/SubjectThreadState.java 
b/core/src/main/java/org/apache/shiro/subject/support/SubjectThreadState.java
index 6e7a891e1..f7514fb7c 100644
--- 
a/core/src/main/java/org/apache/shiro/subject/support/SubjectThreadState.java
+++ 
b/core/src/main/java/org/apache/shiro/subject/support/SubjectThreadState.java
@@ -21,6 +21,7 @@ package org.apache.shiro.subject.support;
 import org.apache.shiro.mgt.SecurityManager;
 import org.apache.shiro.subject.Subject;
 import org.apache.shiro.util.CollectionUtils;
+import org.apache.shiro.util.ScopedValues;
 import org.apache.shiro.util.ThreadContext;
 import org.apache.shiro.util.ThreadState;
 
@@ -44,15 +45,22 @@ public class SubjectThreadState implements ThreadState {
     private Map<Object, Object> originalResources;
 
     private final Subject subject;
-    private transient SecurityManager securityManager;
+    private final SecurityManager securityManager;
 
     /**
      * Creates a new {@code SubjectThreadState} that will bind and unbind the 
specified {@code Subject} to the
      * thread
      *
      * @param subject the {@code Subject} instance to bind and unbind from the 
{@link ThreadContext}.
+     * @throws IllegalStateException if running on JDK 25 or later, where 
{@link ScopedValues} hold the thread state
+     *                               and a {@code ThreadState} would not be 
seen by {@code SecurityUtils}. Use
+     *                               {@link Subject#execute(Runnable)} or 
{@link Subject#associateWith(Runnable)} instead.
      */
     public SubjectThreadState(Subject subject) {
+        if (ScopedValues.INSTANCE.isSupported()) {
+            throw new IllegalStateException("ThreadState is not supported when 
ScopedValues are in use (JDK 25 or later). "
+                    + "Use Subject.execute(), Subject.associateWith() or 
SubjectRunnable/SubjectCallable instead.");
+        }
         if (subject == null) {
             throw new IllegalArgumentException("Subject argument cannot be 
null.");
         }
diff --git 
a/core/src/test/java/org/apache/shiro/mgt/AbstractSecurityManagerTest.java 
b/core/src/test/java/org/apache/shiro/mgt/AbstractSecurityManagerTest.java
index 12a44cfd8..7e6d6c689 100644
--- a/core/src/test/java/org/apache/shiro/mgt/AbstractSecurityManagerTest.java
+++ b/core/src/test/java/org/apache/shiro/mgt/AbstractSecurityManagerTest.java
@@ -21,7 +21,6 @@ package org.apache.shiro.mgt;
 import org.apache.shiro.subject.Subject;
 import org.apache.shiro.subject.support.SubjectThreadState;
 import org.apache.shiro.util.ThreadContext;
-import org.apache.shiro.util.ThreadState;
 import org.junit.jupiter.api.AfterEach;
 
 /**
@@ -29,8 +28,6 @@ import org.junit.jupiter.api.AfterEach;
  */
 public abstract class AbstractSecurityManagerTest {
 
-    protected ThreadState threadState;
-
     @AfterEach
     public void tearDown() {
         ThreadContext.remove();
@@ -38,8 +35,8 @@ public abstract class AbstractSecurityManagerTest {
 
     protected Subject newSubject(SecurityManager securityManager) {
         Subject subject = new Subject.Builder(securityManager).buildSubject();
-        threadState = new SubjectThreadState(subject);
-        threadState.bind();
+        ThreadContext.bind(subject);
+        ThreadContext.bind(SubjectThreadState.getSecurityManager(subject));
         return subject;
     }
 }
diff --git a/core/src/test/java/org/apache/shiro/test/AbstractShiroTest.java 
b/core/src/test/java/org/apache/shiro/test/AbstractShiroTest.java
index 400eb236f..b18ae01b5 100644
--- a/core/src/test/java/org/apache/shiro/test/AbstractShiroTest.java
+++ b/core/src/test/java/org/apache/shiro/test/AbstractShiroTest.java
@@ -23,8 +23,8 @@ import org.apache.shiro.UnavailableSecurityManagerException;
 import org.apache.shiro.mgt.SecurityManager;
 import org.apache.shiro.subject.Subject;
 import org.apache.shiro.subject.support.SubjectThreadState;
+import org.apache.shiro.util.ThreadContext;
 import org.apache.shiro.lang.util.LifecycleUtils;
-import org.apache.shiro.util.ThreadState;
 import org.junit.jupiter.api.AfterAll;
 
 /**
@@ -38,8 +38,6 @@ public abstract class AbstractShiroTest {
      */
     public static final String GLOBAL_SECURITY_MANAGER_RESOURCE = 
"globalSecurityManager";
 
-    private static ThreadState subjectThreadState;
-
     public AbstractShiroTest() {
     }
 
@@ -50,18 +48,14 @@ public abstract class AbstractShiroTest {
      */
     protected void setSubject(Subject subject) {
         clearSubject();
-        subjectThreadState = createThreadState(subject);
-        subjectThreadState.bind();
+        ThreadContext.bind(subject);
+        ThreadContext.bind(SubjectThreadState.getSecurityManager(subject));
     }
 
     protected Subject getSubject() {
         return SecurityUtils.getSubject();
     }
 
-    protected ThreadState createThreadState(Subject subject) {
-        return new SubjectThreadState(subject);
-    }
-
     /**
      * Clears Shiro's thread state, ensuring the thread remains clean for 
future test execution.
      */
@@ -70,10 +64,7 @@ public abstract class AbstractShiroTest {
     }
 
     private static void doClearSubject() {
-        if (subjectThreadState != null) {
-            subjectThreadState.clear();
-            subjectThreadState = null;
-        }
+        ThreadContext.remove();
     }
 
     protected static void setSecurityManager(SecurityManager securityManager) {
diff --git 
a/support/spring/src/test/java/org/apache/shiro/spring/security/interceptor/AbstractAuthorizationAnnotationTest.java
 
b/support/spring/src/test/java/org/apache/shiro/spring/security/interceptor/AbstractAuthorizationAnnotationTest.java
index 224194deb..d98c62f1c 100644
--- 
a/support/spring/src/test/java/org/apache/shiro/spring/security/interceptor/AbstractAuthorizationAnnotationTest.java
+++ 
b/support/spring/src/test/java/org/apache/shiro/spring/security/interceptor/AbstractAuthorizationAnnotationTest.java
@@ -24,7 +24,7 @@ import org.apache.shiro.subject.ImmutablePrincipalCollection;
 import org.apache.shiro.subject.PrincipalCollection;
 import org.apache.shiro.subject.Subject;
 import org.apache.shiro.subject.support.SubjectThreadState;
-import org.apache.shiro.util.ThreadState;
+import org.apache.shiro.util.ThreadContext;
 import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.Test;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -49,19 +49,15 @@ abstract class AbstractAuthorizationAnnotationTest {
     @Autowired
     private Realm realm;
 
-    private ThreadState threadState;
-
     protected void bind(Subject subject) {
         clearSubject();
-        this.threadState = new SubjectThreadState(subject);
-        this.threadState.bind();
+        ThreadContext.bind(subject);
+        ThreadContext.bind(SubjectThreadState.getSecurityManager(subject));
     }
 
     @AfterEach
     public void clearSubject() {
-        if (threadState != null) {
-            threadState.clear();
-        }
+        ThreadContext.remove();
     }
 
     protected void bindGuest() {

Reply via email to