diff --git a/gateway-provider-security-shiro/pom.xml b/gateway-provider-security-shiro/pom.xml
index 8cce8d339b..23a36d3d3b 100644
--- a/gateway-provider-security-shiro/pom.xml
+++ b/gateway-provider-security-shiro/pom.xml
@@ -126,5 +126,10 @@
gateway-test-utils
test
+
+
+ de.thetaphi
+ forbiddenapis
+
-
+
\ No newline at end of file
diff --git a/gateway-provider-security-shiro/src/main/java/org/apache/knox/gateway/filter/ShiroSubjectIdentityAdapter.java b/gateway-provider-security-shiro/src/main/java/org/apache/knox/gateway/filter/ShiroSubjectIdentityAdapter.java
index 0f8ef22b0f..c476f98b76 100644
--- a/gateway-provider-security-shiro/src/main/java/org/apache/knox/gateway/filter/ShiroSubjectIdentityAdapter.java
+++ b/gateway-provider-security-shiro/src/main/java/org/apache/knox/gateway/filter/ShiroSubjectIdentityAdapter.java
@@ -17,6 +17,7 @@
*/
package org.apache.knox.gateway.filter;
+import de.thetaphi.forbiddenapis.SuppressForbidden;
import org.apache.commons.lang3.StringUtils;
import org.apache.knox.gateway.ShiroMessages;
import org.apache.knox.gateway.audit.api.Action;
@@ -44,6 +45,8 @@
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
import java.net.URISyntaxException;
import java.security.Principal;
import java.security.PrivilegedExceptionAction;
@@ -71,6 +74,49 @@ public class ShiroSubjectIdentityAdapter implements Filter {
/* List of URLs with anon authentication */
private static List anonUrls = new ArrayList<>();
+ /*
+ * Subject.callAs(Subject, Callable) was introduced in JDK 18 (JEP 411).
+ * Resolved once at class-load time to avoid per-request reflection overhead.
+ * On JDK 17, SUBJECT_CALL_AS will be null and doSubjectAction() falls back
+ * to Subject.doAs() which still works on JDK 17.
+ * On JDK 23+, Subject.doAs() throws UnsupportedOperationException (KNOX-3338).
+ */
+ private static final Method SUBJECT_CALL_AS;
+
+ static {
+ Method m = null;
+ try {
+ m = javax.security.auth.Subject.class.getMethod(
+ "callAs", javax.security.auth.Subject.class, Callable.class);
+ } catch (NoSuchMethodException | SecurityException e) {
+ // JDK 17 — will fall back to Subject.doAs() in doSubjectAction()
+ }
+ SUBJECT_CALL_AS = m;
+ }
+
+ /**
+ * Executes action under the given subject's identity.
+ * Uses Subject.callAs() on JDK 18+, falls back to Subject.doAs() on JDK 17.
+ * Fixes UnsupportedOperationException on JDK 23+ (KNOX-3338).
+ */
+ @SuppressForbidden
+ private static void doSubjectAction(javax.security.auth.Subject subject,
+ Callable action) throws Exception {
+ if (SUBJECT_CALL_AS != null) {
+ try {
+ SUBJECT_CALL_AS.invoke(null, subject, action);
+ } catch (InvocationTargetException e) {
+ Throwable cause = e.getCause();
+ if (cause instanceof Exception) { throw (Exception) cause; }
+ throw new RuntimeException(cause);
+ }
+ } else {
+ // JDK 17 fallback — deprecated but functional on JDK 17
+ javax.security.auth.Subject.doAs(subject,
+ (PrivilegedExceptionAction) action::call);
+ }
+ }
+
@Override
public void init( FilterConfig filterConfig ) throws ServletException {
/* Create a shiro urls config map */
@@ -130,13 +176,11 @@ private static class CallableChain implements Callable {
@SuppressWarnings("unchecked")
@Override
public Void call() throws Exception {
- PrivilegedExceptionAction action = new PrivilegedExceptionAction() {
- @Override
- public Void run() throws Exception {
+ Callable action = () -> {
chain.doFilter( request, response );
return null;
- }
};
+
Subject shiroSubject = SecurityUtils.getSubject();
/**
@@ -158,7 +202,7 @@ public Void run() throws Exception {
AuditContext context = auditService.getContext();
context.setUsername(principal);
auditService.attachContext(context);
- javax.security.auth.Subject.doAs(subject, action);
+ doSubjectAction(subject, action);
} else {
final String principal = shiroSubject.getPrincipal().toString();
@@ -211,7 +255,7 @@ public Void run() throws Exception {
// To modify the private credential Set, the caller must have AuthPermission("modifyPrivateCredentials").
javax.security.auth.Subject subject = new javax.security.auth.Subject(
true, principals, Collections.emptySet(), Collections.emptySet());
- javax.security.auth.Subject.doAs(subject, action);
+ doSubjectAction(subject, action);
}
return null;
diff --git a/gateway-spi/src/main/java/org/apache/knox/gateway/security/SubjectUtils.java b/gateway-spi/src/main/java/org/apache/knox/gateway/security/SubjectUtils.java
index 9b6b50a62e..5d0ffd9f2f 100644
--- a/gateway-spi/src/main/java/org/apache/knox/gateway/security/SubjectUtils.java
+++ b/gateway-spi/src/main/java/org/apache/knox/gateway/security/SubjectUtils.java
@@ -22,6 +22,7 @@
import javax.security.auth.Subject;
import java.security.AccessController;
+import java.lang.reflect.Method;
import java.security.Principal;
import java.util.Collections;
import java.util.Optional;
@@ -36,9 +37,36 @@ public class SubjectUtils {
* There is no option in JDK 17 other then suppressing.
* For JDK 18+ use Subject.current() instead.
*/
+ /*
+ * Subject.current() was introduced in JDK 18 (JEP 411).
+ * Resolved once at class-load time to avoid per-request reflection overhead.
+ * On JDK 17, SUBJECT_CURRENT_METHOD will be null and we fall back to
+ * the deprecated Subject.getSubject() which still works on JDK 17.
+ * On JDK 23+, the fallback throws UnsupportedOperationException (KNOX-3338).
+ */
+ private static final Method SUBJECT_CURRENT_METHOD;
+
+ static {
+ Method m = null;
+ try {
+ m = Subject.class.getMethod("current"); // available since JDK 18
+ } catch (NoSuchMethodException | SecurityException e) {
+ // JDK 17 — fallback to Subject.getSubject() below
+ }
+ SUBJECT_CURRENT_METHOD = m;
+ }
+
@SuppressForbidden
public static Subject getCurrentSubject() {
- return Subject.getSubject( AccessController.getContext() );
+ if (SUBJECT_CURRENT_METHOD != null) {
+ try {
+ return (Subject) SUBJECT_CURRENT_METHOD.invoke(null);
+ } catch (ReflectiveOperationException e) {
+ throw new RuntimeException("Subject.current() invocation failed", e);
+ }
+ }
+ // JDK 17 fallback — deprecated but functional; throws on JDK 23+
+ return Subject.getSubject(AccessController.getContext());
}
public static String getPrimaryPrincipalName(Subject subject) {