Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion gateway-provider-security-shiro/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -126,5 +126,10 @@
<artifactId>gateway-test-utils</artifactId>
<scope>test</scope>
</dependency>

<dependency>
<groupId>de.thetaphi</groupId>
<artifactId>forbiddenapis</artifactId>
</dependency>
</dependencies>
</project>
</project>
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -71,6 +74,49 @@ public class ShiroSubjectIdentityAdapter implements Filter {
/* List of URLs with anon authentication */
private static List<Matcher> 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<Void> 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<Void>) action::call);
}
}

@Override
public void init( FilterConfig filterConfig ) throws ServletException {
/* Create a shiro urls config map */
Expand Down Expand Up @@ -130,13 +176,11 @@ private static class CallableChain implements Callable<Void> {
@SuppressWarnings("unchecked")
@Override
public Void call() throws Exception {
PrivilegedExceptionAction<Void> action = new PrivilegedExceptionAction<Void>() {
@Override
public Void run() throws Exception {
Callable<Void> action = () -> {
chain.doFilter( request, response );
return null;
}
};

Subject shiroSubject = SecurityUtils.getSubject();

/**
Expand All @@ -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();
Expand Down Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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) {
Expand Down
Loading