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() {
