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
The following commit(s) were added to refs/heads/3.x by this push:
new 4c058a2f7 [#1862] [3.0] Support for JDK 25 scoped values (#2485)
4c058a2f7 is described below
commit 4c058a2f79fa55adc8f850c03b72c4deb3a71d6f
Author: Lenny Primak <[email protected]>
AuthorDate: Sun Feb 22 10:02:35 2026 -0600
[#1862] [3.0] Support for JDK 25 scoped values (#2485)
---
.../main/java/org/apache/shiro/SecurityUtils.java | 14 +-
.../java/org/apache/shiro/subject/Subject.java | 39 +++--
.../shiro/subject/support/SubjectCallable.java | 19 ++-
.../shiro/subject/support/SubjectRunnable.java | 20 ++-
.../shiro/subject/support/SubjectThreadState.java | 21 ++-
.../org/apache/shiro/util/DefaultScopedValues.java | 29 ++++
.../org/apache/shiro/util/JavaEnvironment.java | 181 ---------------------
.../java/org/apache/shiro/util/ScopedValues.java | 55 +++++++
.../org/apache/shiro/util/DefaultScopedValues.java | 60 +++++++
.../apache/shiro/testing/jaxrs/WhoamiResource.java | 16 +-
.../org/apache/shiro/guice/ShiroSessionScope.java | 8 +-
.../org/apache/shiro/ee/filters/ShiroFilter.java | 4 +-
12 files changed, 244 insertions(+), 222 deletions(-)
diff --git a/core/src/main/java/org/apache/shiro/SecurityUtils.java
b/core/src/main/java/org/apache/shiro/SecurityUtils.java
index e53b61b2f..68758c828 100644
--- a/core/src/main/java/org/apache/shiro/SecurityUtils.java
+++ b/core/src/main/java/org/apache/shiro/SecurityUtils.java
@@ -21,6 +21,7 @@ package org.apache.shiro;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.mgt.WrappedSecurityManager;
import org.apache.shiro.subject.Subject;
+import org.apache.shiro.util.ScopedValues;
import org.apache.shiro.util.ThreadContext;
import java.util.Objects;
import java.util.function.Predicate;
@@ -54,6 +55,11 @@ public abstract class SecurityUtils {
* - a Subject should <em>always</em> be
available to the caller.
*/
public static Subject getSubject() {
+ if (ScopedValues.INSTANCE.isSupported() &&
ScopedValues.INSTANCE.isBound()) {
+ return ScopedValues.INSTANCE.get().subject();
+ }
+
+ // fallback to ThreadContext
Subject subject = ThreadContext.getSubject();
if (subject == null) {
subject = (new Subject.Builder()).buildSubject();
@@ -114,6 +120,10 @@ public abstract class SecurityUtils {
* calling code, which
typically indicates an invalid application configuration.
*/
public static SecurityManager getSecurityManager() throws
UnavailableSecurityManagerException {
+ if (ScopedValues.INSTANCE.isSupported() &&
ScopedValues.INSTANCE.isBound()) {
+ return ScopedValues.INSTANCE.get().securityManager();
+ }
+
SecurityManager securityManager = ThreadContext.getSecurityManager();
if (securityManager == null) {
securityManager = SecurityUtils.securityManager;
@@ -178,8 +188,8 @@ public abstract class SecurityUtils {
public static <SM extends SecurityManager> SM
unwrapSecurityManager(SecurityManager securityManager, Class<SM> type,
Predicate<Class<? extends SecurityManager>>
predicate) {
- while (securityManager instanceof WrappedSecurityManager &&
!predicate.test(securityManager.getClass())) {
- WrappedSecurityManager wrappedSecurityManager =
(WrappedSecurityManager) securityManager;
+ while (securityManager instanceof WrappedSecurityManager
wrappedSecurityManager
+ && !predicate.test(securityManager.getClass())) {
securityManager = wrappedSecurityManager.unwrap();
if (securityManager == wrappedSecurityManager) {
throw new IllegalStateException("SecurityManager
implementation of type [" + type.getName()
diff --git a/core/src/main/java/org/apache/shiro/subject/Subject.java
b/core/src/main/java/org/apache/shiro/subject/Subject.java
index b01dae04d..f690cd34b 100644
--- a/core/src/main/java/org/apache/shiro/subject/Subject.java
+++ b/core/src/main/java/org/apache/shiro/subject/Subject.java
@@ -609,18 +609,17 @@ public interface Subject {
*/
private final SubjectContext subjectContext;
- /**
- * The SecurityManager to invoke during the {@link #buildSubject} call.
- */
- private final SecurityManager securityManager;
-
/**
* Constructs a new {@link Subject.Builder} instance, using the {@code
SecurityManager} instance available
* to the calling code as determined by a call to {@link
org.apache.shiro.SecurityUtils#getSecurityManager()}
* to build the {@code Subject} instance.
*/
public Builder() {
- this(SecurityUtils.getSecurityManager());
+ this.subjectContext = newSubjectContextInstance();
+ if (this.subjectContext == null) {
+ throw new IllegalStateException("Subject instance returned
from 'newSubjectContextInstance' "
+ + "cannot be null.");
+ }
}
/**
@@ -630,15 +629,10 @@ public interface Subject {
* @param securityManager the {@code SecurityManager} to use when
building the {@code Subject} instance.
*/
public Builder(SecurityManager securityManager) {
+ this();
if (securityManager == null) {
throw new NullPointerException("SecurityManager method
argument cannot be null.");
}
- this.securityManager = securityManager;
- this.subjectContext = newSubjectContextInstance();
- if (this.subjectContext == null) {
- throw new IllegalStateException("Subject instance returned
from 'newSubjectContextInstance' "
- + "cannot be null.");
- }
this.subjectContext.setSecurityManager(securityManager);
}
@@ -662,6 +656,21 @@ public interface Subject {
return this.subjectContext;
}
+ /**
+ * Uses the provided security manager to build a {@link Subject}
instance based on the
+ * {@link SubjectContext} information collected by this {@code
Builder} instance.
+ *
+ * @return this builder
+ * @throws IllegalStateException if no {@code SecurityManager} is null
+ */
+ public Builder securityManager(SecurityManager securityManager) {
+ if (securityManager == null) {
+ throw new NullPointerException("SecurityManager method
argument cannot be null.");
+ }
+ this.subjectContext.setSecurityManager(securityManager);
+ return this;
+ }
+
/**
* Enables building a {@link Subject Subject} instance that owns the
{@link Session Session} with the
* specified {@code sessionId}.
@@ -841,8 +850,10 @@ public interface Subject {
* other methods in this class.
*/
public Subject buildSubject() {
- return this.securityManager.createSubject(this.subjectContext);
+ if (this.subjectContext.getSecurityManager() == null) {
+
this.subjectContext.setSecurityManager(SecurityUtils.getSecurityManager());
+ }
+ return
this.subjectContext.getSecurityManager().createSubject(this.subjectContext);
}
}
-
}
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 d6d1ecfcb..de2277987 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
@@ -18,7 +18,9 @@
*/
package org.apache.shiro.subject.support;
+import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.subject.Subject;
+import org.apache.shiro.util.ScopedValues;
import org.apache.shiro.util.ThreadState;
import java.util.concurrent.Callable;
@@ -62,13 +64,15 @@ public class SubjectCallable<V> implements Callable<V> {
protected final ThreadState threadState;
private final Callable<V> callable;
+ private final SecurityManager securityManager;
+ private final Subject subject;
public SubjectCallable(Subject subject, Callable<V> delegate) {
- this(new SubjectThreadState(subject), delegate);
+ this(subject, ScopedValues.INSTANCE.isSupported() ? null : new
SubjectThreadState(subject), delegate);
}
- protected SubjectCallable(ThreadState threadState, Callable<V> delegate) {
- if (threadState == null) {
+ protected SubjectCallable(Subject subject, ThreadState threadState,
Callable<V> delegate) {
+ if (threadState == null && !ScopedValues.INSTANCE.isSupported()) {
throw new IllegalArgumentException("ThreadState argument cannot be
null.");
}
this.threadState = threadState;
@@ -76,9 +80,16 @@ public class SubjectCallable<V> implements Callable<V> {
throw new IllegalArgumentException("Callable delegate instance
cannot be null.");
}
this.callable = delegate;
+ this.subject = subject;
+ this.securityManager = SubjectThreadState.getSecurityManager(subject);
}
public V call() throws Exception {
+ if (ScopedValues.INSTANCE.isSupported()) {
+ return ScopedValues.INSTANCE.call(this, callable, subject,
securityManager);
+ }
+
+ // fallback to ThreadState binding if ScopedValues are not available
try {
threadState.bind();
return doCall(this.callable);
@@ -87,7 +98,7 @@ public class SubjectCallable<V> implements Callable<V> {
}
}
- protected V doCall(Callable<V> target) throws Exception {
+ public V doCall(Callable<V> target) throws Exception {
return target.call();
}
}
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 42eeb61bd..7fd93e90d 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
@@ -18,7 +18,9 @@
*/
package org.apache.shiro.subject.support;
+import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.subject.Subject;
+import org.apache.shiro.util.ScopedValues;
import org.apache.shiro.util.ThreadState;
/**
@@ -58,6 +60,8 @@ public class SubjectRunnable implements Runnable {
protected final ThreadState threadState;
private final Runnable runnable;
+ private final SecurityManager securityManager;
+ private final Subject subject;
/**
* Creates a new {@code SubjectRunnable} that, when executed, will execute
the target {@code delegate}, but
@@ -67,7 +71,7 @@ public class SubjectRunnable implements Runnable {
* @param delegate the runnable to run.
*/
public SubjectRunnable(Subject subject, Runnable delegate) {
- this(new SubjectThreadState(subject), delegate);
+ this(subject, ScopedValues.INSTANCE.isSupported() ? null : new
SubjectThreadState(subject), delegate);
}
/**
@@ -79,8 +83,8 @@ public class SubjectRunnable implements Runnable {
* @param delegate the delegate {@code Runnable} to execute when this
instance is {@link #run() run()}.
* @throws IllegalArgumentException if either the {@code ThreadState} or
{@link Runnable} arguments are {@code null}.
*/
- protected SubjectRunnable(ThreadState threadState, Runnable delegate)
throws IllegalArgumentException {
- if (threadState == null) {
+ protected SubjectRunnable(Subject subject, ThreadState threadState,
Runnable delegate) throws IllegalArgumentException {
+ if (threadState == null && !ScopedValues.INSTANCE.isSupported()) {
throw new IllegalArgumentException("ThreadState argument cannot be
null.");
}
this.threadState = threadState;
@@ -88,6 +92,8 @@ public class SubjectRunnable implements Runnable {
throw new IllegalArgumentException("Runnable argument cannot be
null.");
}
this.runnable = delegate;
+ this.subject = subject;
+ this.securityManager = SubjectThreadState.getSecurityManager(subject);
}
/**
@@ -103,6 +109,12 @@ public class SubjectRunnable implements Runnable {
* </pre>
*/
public void run() {
+ if (ScopedValues.INSTANCE.isSupported()) {
+ ScopedValues.INSTANCE.run(this, runnable, subject,
securityManager);
+ return;
+ }
+
+ // fallback to ThreadState binding if ScopedValues are not available
try {
threadState.bind();
doRun(this.runnable);
@@ -116,7 +128,7 @@ public class SubjectRunnable implements Runnable {
*
* @param runnable the target runnable to run.
*/
- protected void doRun(Runnable runnable) {
+ public void doRun(Runnable runnable) {
runnable.run();
}
}
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 43d47259a..1bad717f8 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
@@ -57,15 +57,7 @@ public class SubjectThreadState implements ThreadState {
throw new IllegalArgumentException("Subject argument cannot be
null.");
}
this.subject = subject;
-
- SecurityManager securityManager = null;
- if (subject instanceof DelegatingSubject delegatingSubject) {
- securityManager = delegatingSubject.getSecurityManager();
- }
- if (securityManager == null) {
- securityManager = ThreadContext.getSecurityManager();
- }
- this.securityManager = securityManager;
+ this.securityManager = getSecurityManager(subject);
}
/**
@@ -121,4 +113,15 @@ public class SubjectThreadState implements ThreadState {
public void clear() {
ThreadContext.remove();
}
+
+ public static SecurityManager getSecurityManager(Subject subject) {
+ SecurityManager securityManager = null;
+ if (subject instanceof DelegatingSubject delegatingSubject) {
+ securityManager = delegatingSubject.getSecurityManager();
+ }
+ if (securityManager == null) {
+ securityManager = ThreadContext.getSecurityManager();
+ }
+ return securityManager;
+ }
}
diff --git a/core/src/main/java/org/apache/shiro/util/DefaultScopedValues.java
b/core/src/main/java/org/apache/shiro/util/DefaultScopedValues.java
new file mode 100644
index 000000000..e12ec6bb8
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/util/DefaultScopedValues.java
@@ -0,0 +1,29 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.util;
+
+/**
+ * Overridden via MR-Jar to provide a Java 25 implementation of {@link
ScopedValues} when running on Java 25 or later.
+ */
+final class DefaultScopedValues implements ScopedValues {
+ @Override
+ public boolean isSupported() {
+ return false;
+ }
+}
diff --git a/core/src/main/java/org/apache/shiro/util/JavaEnvironment.java
b/core/src/main/java/org/apache/shiro/util/JavaEnvironment.java
deleted file mode 100644
index 1735a5b0e..000000000
--- a/core/src/main/java/org/apache/shiro/util/JavaEnvironment.java
+++ /dev/null
@@ -1,181 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.shiro.util;
-
-/**
- * Internal helper class used to find the Java/JDK version
- * that Shiro is operating within, to allow for automatically
- * adapting to the present platform's capabilities.
- *
- * <p>Note that Shiro does not support 1.2 or earlier JVMs - only 1.3 and
later.
- *
- * <p><em>This class was borrowed and heavily based upon a nearly identical
version found in
- * the <a href="http://www.springframework.org/">Spring Framework</a>, with
minor modifications.
- * The original author names and copyright (Apache 2.0) has been left in
place. A special
- * thanks to Rod Johnson, Juergen Hoeller, and Rick Evans for making this
available.</em>
- *
- * @since 0.2
- * @deprecated This class is no longer used in Shiro and will be removed in
the next major version.
- */
-@Deprecated
-public abstract class JavaEnvironment {
-
- /**
- * Constant identifying the 1.3.x JVM (JDK 1.3).
- */
- public static final int JAVA_13 = 0;
-
- /**
- * Constant identifying the 1.4.x JVM (J2SE 1.4).
- */
- public static final int JAVA_14 = 1;
-
- /**
- * Constant identifying the 1.5 JVM (Java 5).
- */
- public static final int JAVA_15 = 2;
-
- /**
- * Constant identifying the 1.6 JVM (Java 6).
- */
- public static final int JAVA_16 = 3;
-
- /**
- * Constant identifying the 1.7 JVM.
- */
- public static final int JAVA_17 = 4;
-
- /**
- * Constant identifying the 1.8 JVM.
- */
- public static final int JAVA_18 = 5;
-
- /**
- * The virtual machine version, i.e.
<code>System.getProperty("java.version");</code>.
- */
- private static final String VERSION;
-
- /**
- * The virtual machine <em>major</em> version. For example, with a
<code>version</code> of
- * <code>1.5.6_10</code>, this would be <code>1.5</code>
- */
- private static final int MAJOR_VERSION;
-
- /**
- * Static code initialization block that sets the
- * <code>version</code> and <code>majorVersion</code> Class constants
- * upon initialization.
- */
- static {
- VERSION = System.getProperty("java.version");
- // version String should look like "1.4.2_10"
-
-// NOTE: JDK 1.9 will be versioned differently '9' and/or 9.x.x
-// https://blogs.oracle.com/java-platform-group/entry/a_new_jdk_9_version
-
- if (VERSION.contains("1.8.")) {
- MAJOR_VERSION = JAVA_18;
- } else if (VERSION.contains("1.7.")) {
- MAJOR_VERSION = JAVA_17;
- } else if (VERSION.contains("1.6.")) {
- MAJOR_VERSION = JAVA_16;
- } else if (VERSION.contains("1.5.")) {
- MAJOR_VERSION = JAVA_15;
- } else if (VERSION.contains("1.4.")) {
- MAJOR_VERSION = JAVA_14;
- } else {
- // else leave 1.3 as default (it's either 1.3 or unknown)
- MAJOR_VERSION = JAVA_13;
- }
- }
-
-
- /**
- * Return the full Java version string, as returned by
- * <code>System.getProperty("java.version")</code>.
- *
- * @return the full Java version string
- * @see System#getProperty(String)
- */
- public static String getVersion() {
- return VERSION;
- }
-
- /**
- * Get the major version code. This means we can do things like
- * <code>if (getMajorVersion() < JAVA_14)</code>.
- *
- * @return a code comparable to the JAVA_XX codes in this class
- * @see #JAVA_13
- * @see #JAVA_14
- * @see #JAVA_15
- * @see #JAVA_16
- * @see #JAVA_17
- * @see #JAVA_18
- */
- public static int getMajorVersion() {
- return MAJOR_VERSION;
- }
-
- /**
- * Convenience method to determine if the current JVM is at least Java 1.4.
- *
- * @return <code>true</code> if the current JVM is at least Java 1.4
- * @see #getMajorVersion()
- * @see #JAVA_14
- * @see #JAVA_15
- * @see #JAVA_16
- * @see #JAVA_17
- * @see #JAVA_18
- */
- public static boolean isAtLeastVersion14() {
- return getMajorVersion() >= JAVA_14;
- }
-
- /**
- * Convenience method to determine if the current JVM is at least
- * Java 1.5 (Java 5).
- *
- * @return <code>true</code> if the current JVM is at least Java 1.5
- * @see #getMajorVersion()
- * @see #JAVA_15
- * @see #JAVA_16
- * @see #JAVA_17
- * @see #JAVA_18
- */
- public static boolean isAtLeastVersion15() {
- return getMajorVersion() >= JAVA_15;
- }
-
- /**
- * Convenience method to determine if the current JVM is at least
- * Java 1.6 (Java 6).
- *
- * @return <code>true</code> if the current JVM is at least Java 1.6
- * @see #getMajorVersion()
- * @see #JAVA_15
- * @see #JAVA_16
- * @see #JAVA_17
- * @see #JAVA_18
- * @since 1.2
- */
- public static boolean isAtLeastVersion16() {
- return getMajorVersion() >= JAVA_16;
- }
-}
diff --git a/core/src/main/java/org/apache/shiro/util/ScopedValues.java
b/core/src/main/java/org/apache/shiro/util/ScopedValues.java
new file mode 100644
index 000000000..4cbb1107a
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/util/ScopedValues.java
@@ -0,0 +1,55 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.util;
+
+import org.apache.shiro.mgt.SecurityManager;
+import org.apache.shiro.subject.Subject;
+import org.apache.shiro.subject.support.SubjectCallable;
+import org.apache.shiro.subject.support.SubjectRunnable;
+import java.util.concurrent.Callable;
+
+/**
+ * An abstraction over Java 25's ScopedValue to allow Shiro to use it when
running on Java 25 or later,
+ * but degrade gracefully to ThreadLocals when running on older Java versions.
+ * This is used for Subject and Security manager to allow them to be
propagated across thread boundaries.
+ */
+public interface ScopedValues {
+ record Values(SecurityManager securityManager, Subject subject) { }
+
+ ScopedValues INSTANCE = new DefaultScopedValues();
+
+ boolean isSupported();
+
+ default boolean isBound() {
+ throw new IllegalStateException("ScopedValues are not supported in
this Java version");
+ }
+
+ default Values get() {
+ throw new IllegalStateException("ScopedValues are not supported in
this Java version");
+ }
+
+ default <T> T call(SubjectCallable<T> callable, Callable<T> target,
+ Subject subject, SecurityManager securityManager) throws
Exception {
+ throw new IllegalStateException("ScopedValues are not supported in
this Java version");
+ }
+
+ default void run(SubjectRunnable runnable, Runnable target, Subject
subject, SecurityManager securityManager) {
+ throw new IllegalStateException("ScopedValues are not supported in
this Java version");
+ }
+}
diff --git
a/core/src/main/java25/org/apache/shiro/util/DefaultScopedValues.java
b/core/src/main/java25/org/apache/shiro/util/DefaultScopedValues.java
new file mode 100644
index 000000000..2514a12bf
--- /dev/null
+++ b/core/src/main/java25/org/apache/shiro/util/DefaultScopedValues.java
@@ -0,0 +1,60 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.util;
+
+import org.apache.shiro.mgt.SecurityManager;
+import org.apache.shiro.subject.Subject;
+import org.apache.shiro.subject.support.SubjectCallable;
+import org.apache.shiro.subject.support.SubjectRunnable;
+import java.util.concurrent.Callable;
+
+/**
+ * Default {@link ScopedValues} implementation that uses Java 25's {@link
ScopedValue} to store the Subject and SecurityManager
+ */
+final class DefaultScopedValues implements ScopedValues {
+ private static final ScopedValue<Values> VALUES =
ScopedValue.newInstance();
+
+ @Override
+ public boolean isSupported() {
+ return true;
+ }
+
+ @Override
+ public boolean isBound() {
+ return VALUES.isBound();
+ }
+
+ @Override
+ public Values get() {
+ return VALUES.get();
+ }
+
+ @Override
+ public <T> T call(SubjectCallable<T> callable, Callable<T> target,
+ Subject subject, SecurityManager securityManager)
throws Exception {
+ return ScopedValue.where(VALUES, new Values(securityManager, subject))
+ .call(() -> callable.doCall(target));
+ }
+
+ @Override
+ public void run(SubjectRunnable runnable, Runnable target, Subject
subject, SecurityManager securityManager) {
+ ScopedValue.where(VALUES, new Values(securityManager, subject))
+ .run(() -> runnable.doRun(target));
+ }
+}
diff --git
a/integration-tests/jakarta-ee/src/main/java/org/apache/shiro/testing/jaxrs/WhoamiResource.java
b/integration-tests/jakarta-ee/src/main/java/org/apache/shiro/testing/jaxrs/WhoamiResource.java
index 567a4b077..eff0860b9 100644
---
a/integration-tests/jakarta-ee/src/main/java/org/apache/shiro/testing/jaxrs/WhoamiResource.java
+++
b/integration-tests/jakarta-ee/src/main/java/org/apache/shiro/testing/jaxrs/WhoamiResource.java
@@ -28,6 +28,7 @@ import jakarta.ws.rs.core.Response.Status;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.lang.ShiroException;
+import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.ThreadContext;
@Path("whoami")
@@ -76,13 +77,20 @@ public class WhoamiResource {
private <T> T check(Supplier<T> happy, Supplier<T> sad, String user,
String password) {
try {
- ThreadContext.bind(testApplication.getSecurityManager());
- SecurityUtils.getSubject().login(new UsernamePasswordToken(user,
password));
- return happy.get();
+ return new Subject.Builder()
+ .securityManager(testApplication.getSecurityManager())
+ .buildSubject()
+ .execute(() -> {
+ SecurityUtils.getSubject().login(new
UsernamePasswordToken(user, password));
+ try {
+ return happy.get();
+ } finally {
+ SecurityUtils.getSubject().logout();
+ }
+ });
} catch (ShiroException e) {
return sad.get();
} finally {
- SecurityUtils.getSubject().logout();
ThreadContext.unbindSecurityManager();
ThreadContext.unbindSubject();
ThreadContext.remove();
diff --git
a/support/guice/src/main/java/org/apache/shiro/guice/ShiroSessionScope.java
b/support/guice/src/main/java/org/apache/shiro/guice/ShiroSessionScope.java
index 0e5b92c88..7f2a46d7d 100644
--- a/support/guice/src/main/java/org/apache/shiro/guice/ShiroSessionScope.java
+++ b/support/guice/src/main/java/org/apache/shiro/guice/ShiroSessionScope.java
@@ -24,6 +24,7 @@ import com.google.inject.Provider;
import com.google.inject.Scope;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.Subject;
+import org.apache.shiro.util.ScopedValues;
import org.apache.shiro.util.ThreadContext;
/**
@@ -33,7 +34,12 @@ public class ShiroSessionScope implements Scope {
public <T> Provider<T> scope(final Key<T> key, final Provider<T> unscoped)
{
return new Provider<T>() {
public T get() {
- Subject subject = ThreadContext.getSubject();
+ Subject subject;
+ if (ScopedValues.INSTANCE.isSupported() &&
ScopedValues.INSTANCE.isBound()) {
+ subject = ScopedValues.INSTANCE.get().subject();
+ } else {
+ subject = ThreadContext.getSubject();
+ }
if (subject == null) {
throw new OutOfScopeException("There is no Shiro Session
currently in scope.");
}
diff --git
a/support/jakarta-ee/src/main/java/org/apache/shiro/ee/filters/ShiroFilter.java
b/support/jakarta-ee/src/main/java/org/apache/shiro/ee/filters/ShiroFilter.java
index 0742d25b1..ea5cee73c 100644
---
a/support/jakarta-ee/src/main/java/org/apache/shiro/ee/filters/ShiroFilter.java
+++
b/support/jakarta-ee/src/main/java/org/apache/shiro/ee/filters/ShiroFilter.java
@@ -47,7 +47,6 @@ import lombok.SneakyThrows;
import lombok.experimental.Delegate;
import lombok.extern.slf4j.Slf4j;
import static
org.apache.shiro.ee.listeners.EnvironmentLoaderListener.isServletNoPrincipal;
-import org.apache.shiro.mgt.DefaultSecurityManager;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.session.Session;
import org.apache.shiro.session.SessionException;
@@ -169,8 +168,7 @@ public class ShiroFilter extends
org.apache.shiro.web.servlet.ShiroFilter {
@Override
public Subject createSubject(SubjectContext context) {
- if (context instanceof WebSubjectContext webContext && wrapped
instanceof DefaultSecurityManager) {
- DefaultWebSecurityManager wsm = (DefaultWebSecurityManager)
wrapped;
+ if (context instanceof WebSubjectContext webContext && wrapped
instanceof DefaultWebSecurityManager wsm) {
Session session = null;
try {
session = wsm.getSession(new
WebSessionKey(webContext.getSessionId(), webContext.getServletRequest(),