From 05a02f2ab594c51377bb09d9681780f0e4aa4aa0 Mon Sep 17 00:00:00 2001 From: arunk-kumar Date: Sat, 20 Jun 2026 18:25:35 +0530 Subject: [PATCH 1/2] KNOX-3338: Fix UnsupportedOperationException on JDK 23+ via reflective Subject.current lookup --- .../java/org/apache/knox/gateway/security/SubjectUtils.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) 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..568698e198 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 @@ -38,7 +38,11 @@ public class SubjectUtils { */ @SuppressForbidden public static Subject getCurrentSubject() { - return Subject.getSubject( AccessController.getContext() ); + try { + return (Subject) Subject.class.getMethod("current").invoke(null); + } catch (Exception e) { + return Subject.getSubject( AccessController.getContext() ); + } } public static String getPrimaryPrincipalName(Subject subject) { From 11cc435ae608dc7e5a91bd9f793facd551730308 Mon Sep 17 00:00:00 2001 From: arunk-kumar Date: Wed, 24 Jun 2026 11:38:34 +0530 Subject: [PATCH 2/2] KNOX-3338 - Replace deprecated Subject.getSubject/doAs with JDK 18+ APIs - SubjectUtils: cache Subject.current() via static initializer, fall back to Subject.getSubject() on JDK 17 - ShiroSubjectIdentityAdapter: add SUBJECT_CALL_AS static cache and doSubjectAction() helper; replace both Subject.doAs() call sites; PrivilegedExceptionAction replaced with Callable lambda - Add forbiddenapis compile dependency to gateway-provider-security-shiro - Both files compile on JDK 17 and run correctly on JDK 23+ - Catches NoSuchMethodException|SecurityException in static blocks --- gateway-provider-security-shiro/pom.xml | 7 ++- .../filter/ShiroSubjectIdentityAdapter.java | 56 +++++++++++++++++-- .../knox/gateway/security/SubjectUtils.java | 32 +++++++++-- 3 files changed, 84 insertions(+), 11 deletions(-) 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 568698e198..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,13 +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() { - try { - return (Subject) Subject.class.getMethod("current").invoke(null); - } catch (Exception e) { - 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) {