This is an automated email from the ASF dual-hosted git repository.

markt-asf pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tomcat.git


The following commit(s) were added to refs/heads/main by this push:
     new 3a1c06bc34 Refactor JASPIC caching
3a1c06bc34 is described below

commit 3a1c06bc349668e52e8f0eca66d4469ff71fae8d
Author: Mark Thomas <[email protected]>
AuthorDate: Tue Aug 18 18:42:49 2026 +0100

    Refactor JASPIC caching
---
 .../catalina/authenticator/AuthenticatorBase.java  | 258 +++++++++++++--------
 .../jaspic/SimpleAuthConfigProvider.java           |  25 +-
 webapps/docs/changelog.xml                         |   6 +
 3 files changed, 164 insertions(+), 125 deletions(-)

diff --git a/java/org/apache/catalina/authenticator/AuthenticatorBase.java 
b/java/org/apache/catalina/authenticator/AuthenticatorBase.java
index 738f1754fb..9378267a08 100644
--- a/java/org/apache/catalina/authenticator/AuthenticatorBase.java
+++ b/java/org/apache/catalina/authenticator/AuthenticatorBase.java
@@ -22,6 +22,7 @@ import java.util.Locale;
 import java.util.Map;
 import java.util.Optional;
 import java.util.Set;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
 
 import javax.security.auth.Subject;
 import javax.security.auth.callback.CallbackHandler;
@@ -237,8 +238,10 @@ public abstract class AuthenticatorBase extends ValveBase 
implements Authenticat
     private AllowCorsPreflight allowCorsPreflight = AllowCorsPreflight.NEVER;
 
     private volatile String jaspicAppContextID = null;
-    private volatile Optional<AuthConfigProvider> jaspicProvider = null;
-    private volatile CallbackHandler jaspicCallbackHandler = null;
+    // Lock to ensure each request sees a consistent JASPIC state
+    private final ReentrantReadWriteLock jaspicLock = new 
ReentrantReadWriteLock();
+    // The per context (web application) state
+    private Optional<JaspicContextState> jaspicContextState = null;
 
 
     // ------------------------------------------------------------- Properties
@@ -523,8 +526,10 @@ public abstract class AuthenticatorBase extends ValveBase 
implements Authenticat
         // Is this request URI subject to a security constraint?
         SecurityConstraint[] constraints = 
realm.findSecurityConstraints(request, this.context);
 
-        AuthConfigProvider jaspicProvider = getJaspicProvider();
-        if (jaspicProvider != null) {
+        JaspicContextState jaspicContextState = getJaspicContextState();
+
+        // Will be non-null if JASPIC is configured for this context
+        if (jaspicContextState != null) {
             authRequired = true;
         }
 
@@ -573,8 +578,6 @@ public abstract class AuthenticatorBase extends ValveBase 
implements Authenticat
             authRequired = true;
         }
 
-        JaspicState jaspicState = null;
-
         if ((authRequired || constraints != null) && 
allowCorsPreflightBypass(request)) {
             if (log.isDebugEnabled()) {
                 log.debug(sm.getString("authenticator.corsBypass"));
@@ -583,13 +586,15 @@ public abstract class AuthenticatorBase extends ValveBase 
implements Authenticat
             return;
         }
 
+        JaspicRequestState jaspicRequestState = null;
+
         if (authRequired) {
             if (log.isTraceEnabled()) {
                 log.trace("Calling authenticate()");
             }
 
             boolean authenticated;
-            if (jaspicProvider == null) {
+            if (jaspicContextState == null) {
                 AuthenticationResult authenticationResult = 
doAuthenticateExtended(request, response);
                 if (authenticationResult == 
AuthenticationResult.PASSED_CONSTRAINTS_NEED_REFRESH) {
                     // Recalculate constraints since the request has changed
@@ -603,11 +608,18 @@ public abstract class AuthenticatorBase extends ValveBase 
implements Authenticat
                 }
                 authenticated = authenticationResult.getAuthenticated();
             } else {
-                jaspicState = getJaspicState(jaspicProvider, request, 
response, hasAuthConstraint);
-                if (jaspicState == null) {
+                if (jaspicContextState.serverAuthConfig() == null) {
+                    // JASPIC is configured but didn't initialise correctly
+                    
response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
                     return;
                 }
-                authenticated = authenticateJaspic(request, response, 
jaspicState, false);
+                jaspicRequestState = getJaspicRequestState(
+                        jaspicContextState.serverAuthConfig(), request, 
response, hasAuthConstraint);
+                if (jaspicRequestState == null) {
+                    // getJaspicRequestState() sets the HTTP status code
+                    return;
+                }
+                authenticated = authenticateJaspic(request, response, 
jaspicRequestState, false);
             }
 
             if (!authenticated) {
@@ -646,8 +658,8 @@ public abstract class AuthenticatorBase extends ValveBase 
implements Authenticat
         }
         getNext().invoke(request, response);
 
-        if (jaspicProvider != null) {
-            secureResponseJspic(request, response, jaspicState);
+        if (jaspicContextState != null) {
+            secureResponseJaspic(request, response, jaspicRequestState);
         }
     }
 
@@ -751,28 +763,35 @@ public abstract class AuthenticatorBase extends ValveBase 
implements Authenticat
     @Override
     public boolean authenticate(Request request, HttpServletResponse 
httpResponse) throws IOException {
 
-        AuthConfigProvider jaspicProvider = getJaspicProvider();
+        JaspicContextState jaspicContextState = getJaspicContextState();
 
-        if (jaspicProvider == null) {
+        if (jaspicContextState == null) {
             // Just authenticating so no requirement to refresh constraints
             return doAuthenticateExtended(request, 
httpResponse).getAuthenticated();
         } else {
             Response response = request.getResponse();
-            JaspicState jaspicState = getJaspicState(jaspicProvider, request, 
response, true);
-            if (jaspicState == null) {
+            if (jaspicContextState.serverAuthConfig() == null) {
+                // JASPIC is configured but didn't initialise correctly
+                
response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
+                return false;
+            }
+            JaspicRequestState jaspicRequestState =
+                    
getJaspicRequestState(jaspicContextState.serverAuthConfig(), request, response, 
true);
+            if (jaspicRequestState == null) {
+                // getJaspicRequestState() sets the HTTP status code
                 return false;
             }
 
-            boolean result = authenticateJaspic(request, response, 
jaspicState, true);
+            boolean result = authenticateJaspic(request, response, 
jaspicRequestState, true);
 
-            secureResponseJspic(request, response, jaspicState);
+            secureResponseJaspic(request, response, jaspicRequestState);
 
             return result;
         }
     }
 
 
-    private void secureResponseJspic(Request request, Response response, 
JaspicState state) {
+    private void secureResponseJaspic(Request request, Response response, 
JaspicRequestState state) {
         try {
             state.serverAuthContext.secureResponse(state.messageInfo, null);
             request.setRequest((HttpServletRequest) 
state.messageInfo.getRequestMessage());
@@ -783,63 +802,20 @@ public abstract class AuthenticatorBase extends ValveBase 
implements Authenticat
     }
 
 
-    private JaspicState getJaspicState(AuthConfigProvider jaspicProvider, 
Request request, Response response,
-            boolean authMandatory) throws IOException {
-        JaspicState jaspicState = new JaspicState();
+    private JaspicRequestState getJaspicRequestState(ServerAuthConfig 
serverAuthConfig, Request request,
+            Response response, boolean authMandatory) throws IOException {
 
-        jaspicState.messageInfo = new MessageInfoImpl(request.getRequest(), 
response.getResponse(), authMandatory);
+        MessageInfo messageInfo = new MessageInfoImpl(request.getRequest(), 
response.getResponse(), authMandatory);
 
         try {
-            CallbackHandler callbackHandler = getCallbackHandler();
-            ServerAuthConfig serverAuthConfig =
-                    jaspicProvider.getServerAuthConfig("HttpServlet", 
jaspicAppContextID, callbackHandler);
-            String authContextID = 
serverAuthConfig.getAuthContextID(jaspicState.messageInfo);
-            jaspicState.serverAuthContext = 
serverAuthConfig.getAuthContext(authContextID, null, null);
+            String authContextID = 
serverAuthConfig.getAuthContextID(messageInfo);
+            ServerAuthContext serverAuthContext = 
serverAuthConfig.getAuthContext(authContextID, null, null);
+            return new JaspicRequestState(messageInfo, serverAuthContext);
         } catch (AuthException e) {
             
log.warn(sm.getString("authenticator.jaspicServerAuthContextFail"), e);
             response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
             return null;
         }
-
-        return jaspicState;
-    }
-
-
-    private CallbackHandler getCallbackHandler() {
-        CallbackHandler handler = jaspicCallbackHandler;
-        if (handler == null) {
-            handler = createCallbackHandler();
-        }
-        return handler;
-    }
-
-
-    private CallbackHandler createCallbackHandler() {
-        CallbackHandler callbackHandler;
-
-        Class<?> clazz = null;
-        try {
-            clazz = Class.forName(jaspicCallbackHandlerClass, true, 
Thread.currentThread().getContextClassLoader());
-        } catch (ClassNotFoundException ignore) {
-            // Not found in the context class loader (web application class 
loader). Re-try below.
-        }
-
-        try {
-            if (clazz == null) {
-                // Look in the same class loader that loaded this class - 
usually Tomcat's common loader.
-                clazz = Class.forName(jaspicCallbackHandlerClass);
-            }
-            callbackHandler = (CallbackHandler) 
clazz.getConstructor().newInstance();
-        } catch (ReflectiveOperationException e) {
-            throw new SecurityException(e);
-        }
-
-        if (callbackHandler instanceof Contained) {
-            ((Contained) callbackHandler).setContainer(getContainer());
-        }
-
-        jaspicCallbackHandler = callbackHandler;
-        return callbackHandler;
     }
 
 
@@ -910,7 +886,7 @@ public abstract class AuthenticatorBase extends ValveBase 
implements Authenticat
     }
 
 
-    private boolean authenticateJaspic(Request request, Response response, 
JaspicState state,
+    private boolean authenticateJaspic(Request request, Response response, 
JaspicRequestState state,
             boolean requirePrincipal) {
 
         boolean cachedAuth = checkForCachedAuthentication(request, response, 
false);
@@ -1340,20 +1316,23 @@ public abstract class AuthenticatorBase extends 
ValveBase implements Authenticat
 
     @Override
     public void logout(Request request) {
-        AuthConfigProvider provider = getJaspicProvider();
-        if (provider != null) {
+        JaspicContextState jaspicContextState = getJaspicContextState();
+        if (jaspicContextState != null) {
             MessageInfo messageInfo = new MessageInfoImpl(request, 
request.getResponse(), true);
             Subject client = (Subject) 
request.getNote(Constants.REQ_JASPIC_SUBJECT_NOTE);
             if (client != null) {
-                ServerAuthContext serverAuthContext;
-                try {
-                    ServerAuthConfig serverAuthConfig =
-                            provider.getServerAuthConfig("HttpServlet", 
jaspicAppContextID, getCallbackHandler());
-                    String authContextID = 
serverAuthConfig.getAuthContextID(messageInfo);
-                    serverAuthContext = 
serverAuthConfig.getAuthContext(authContextID, null, null);
-                    serverAuthContext.cleanSubject(messageInfo, client);
-                } catch (AuthException e) {
-                    
log.debug(sm.getString("authenticator.jaspicCleanSubjectFail"), e);
+                ServerAuthConfig serverAuthConfig = 
jaspicContextState.serverAuthConfig();
+                if (serverAuthConfig == null) {
+                    
log.debug(sm.getString("authenticator.jaspicCleanSubjectFail"));
+                } else {
+                    try {
+                        String authContextID = 
serverAuthConfig.getAuthContextID(messageInfo);
+                        ServerAuthContext serverAuthContext =
+                                serverAuthConfig.getAuthContext(authContextID, 
null, null);
+                        serverAuthContext.cleanSubject(messageInfo, client);
+                    } catch (AuthException e) {
+                        
log.debug(sm.getString("authenticator.jaspicCleanSubjectFail"), e);
+                    }
                 }
             }
         }
@@ -1428,40 +1407,113 @@ public abstract class AuthenticatorBase extends 
ValveBase implements Authenticat
     }
 
 
-    private AuthConfigProvider getJaspicProvider() {
-        Optional<AuthConfigProvider> provider = jaspicProvider;
-        if (provider == null) {
-            provider = findJaspicProvider();
+    @Override
+    public void notify(String layer, String appContext) {
+        jaspicLock.writeLock().lock();
+        try {
+            jaspicContextState = null;
+        } finally {
+            jaspicLock.writeLock().unlock();
         }
-        return provider.orElse(null);
     }
 
 
-    private Optional<AuthConfigProvider> findJaspicProvider() {
-        AuthConfigFactory factory = AuthConfigFactory.getFactory();
-        Optional<AuthConfigProvider> provider;
-        if (factory == null) {
-            provider = Optional.empty();
-        } else {
-            provider = 
Optional.ofNullable(factory.getConfigProvider("HttpServlet", 
jaspicAppContextID, this));
+    private JaspicContextState getJaspicContextState() {
+        jaspicLock.readLock().lock();
+        try {
+            if (jaspicContextState != null) {
+                // A previous result has been cached. Use it.
+                JaspicContextState result = jaspicContextState.orElse(null);
+                if (result == null) {
+                    // No JASPIC provider so return null
+                    return null;
+                }
+                if (result.serverAuthConfig != null) {
+                    return result;
+                }
+            }
+        } finally {
+            jaspicLock.readLock().unlock();
+        }
+
+        jaspicLock.writeLock().lock();
+        try {
+            if (jaspicContextState == null) {
+                AuthConfigFactory factory = AuthConfigFactory.getFactory();
+                if (factory == null) {
+                    jaspicContextState = Optional.empty();
+                } else {
+                    AuthConfigProvider authConfigProvider =
+                            factory.getConfigProvider("HttpServlet", 
jaspicAppContextID, this);
+                    if (authConfigProvider == null) {
+                        jaspicContextState = Optional.empty();
+                    } else {
+                        jaspicContextState = Optional.of(
+                                new JaspicContextState(authConfigProvider, 
createServerAuthConfig(authConfigProvider)));
+                    }
+                }
+            } else {
+                JaspicContextState result = jaspicContextState.orElse(null);
+                if (result == null) {
+                    return null;
+                }
+                if (result.serverAuthConfig == null) {
+                    jaspicContextState = Optional.of(new JaspicContextState(
+                            result.authConfigProvider(), 
createServerAuthConfig(result.authConfigProvider())));
+                }
+            }
+            return jaspicContextState.orElse(null);
+
+        } finally {
+            jaspicLock.writeLock().unlock();
         }
-        jaspicProvider = provider;
-        return provider;
     }
 
 
-    @Override
-    public void notify(String layer, String appContext) {
-        findJaspicProvider();
+    private ServerAuthConfig createServerAuthConfig(AuthConfigProvider 
authConfigProvider) {
+        CallbackHandler callbackHandler = createCallbackHandler();
+        try {
+            return authConfigProvider.getServerAuthConfig("HttpServlet", 
jaspicAppContextID, callbackHandler);
+        } catch (AuthException e) {
+            
log.warn(sm.getString("authenticator.jaspicServerAuthContextFail"), e);
+        }
+        return null;
     }
 
 
-    /**
-     * Holds the state for a JASPIC authentication interaction.
-     */
-    private static class JaspicState {
-        public MessageInfo messageInfo = null;
-        public ServerAuthContext serverAuthContext = null;
+    private CallbackHandler createCallbackHandler() {
+        CallbackHandler callbackHandler;
+
+        Class<?> clazz = null;
+        try {
+            clazz = Class.forName(jaspicCallbackHandlerClass, true, 
Thread.currentThread().getContextClassLoader());
+        } catch (ClassNotFoundException ignore) {
+            // Not found in the context class loader (web application class 
loader). Re-try below.
+        }
+
+        try {
+            if (clazz == null) {
+                // Look in the same class loader that loaded this class - 
usually Tomcat's common loader.
+                clazz = Class.forName(jaspicCallbackHandlerClass);
+            }
+            callbackHandler = (CallbackHandler) 
clazz.getConstructor().newInstance();
+        } catch (ReflectiveOperationException e) {
+            throw new SecurityException(e);
+        }
+
+        if (callbackHandler instanceof Contained) {
+            ((Contained) callbackHandler).setContainer(getContainer());
+        }
+
+        return callbackHandler;
+    }
+
+
+    private record JaspicContextState(AuthConfigProvider authConfigProvider, 
ServerAuthConfig serverAuthConfig) {
+    }
+
+
+    private record JaspicRequestState(MessageInfo messageInfo, 
ServerAuthContext serverAuthContext) {
     }
 
 
diff --git 
a/java/org/apache/catalina/authenticator/jaspic/SimpleAuthConfigProvider.java 
b/java/org/apache/catalina/authenticator/jaspic/SimpleAuthConfigProvider.java
index 7a5c7c1b8c..562ba702f6 100644
--- 
a/java/org/apache/catalina/authenticator/jaspic/SimpleAuthConfigProvider.java
+++ 
b/java/org/apache/catalina/authenticator/jaspic/SimpleAuthConfigProvider.java
@@ -34,8 +34,6 @@ public class SimpleAuthConfigProvider implements 
AuthConfigProvider {
 
     private final Map<String,Object> properties;
 
-    private volatile ServerAuthConfig serverAuthConfig;
-
     /**
      * Creates a new SimpleAuthConfigProvider.
      *
@@ -62,24 +60,10 @@ public class SimpleAuthConfigProvider implements 
AuthConfigProvider {
     }
 
 
-    /**
-     * {@inheritDoc}
-     * <p>
-     * The returned ServerAuthConfig is created lazily and cached.
-     */
     @Override
     public ServerAuthConfig getServerAuthConfig(String layer, String 
appContext, CallbackHandler handler)
             throws AuthException {
-        ServerAuthConfig serverAuthConfig = this.serverAuthConfig;
-        if (serverAuthConfig == null) {
-            synchronized (this) {
-                if (this.serverAuthConfig == null) {
-                    this.serverAuthConfig = createServerAuthConfig(layer, 
appContext, handler, properties);
-                }
-                serverAuthConfig = this.serverAuthConfig;
-            }
-        }
-        return serverAuthConfig;
+        return createServerAuthConfig(layer, appContext, handler, properties);
     }
 
 
@@ -102,13 +86,10 @@ public class SimpleAuthConfigProvider implements 
AuthConfigProvider {
     /**
      * {@inheritDoc}
      * <p>
-     * Delegates refresh to the cached ServerAuthConfig if one has been 
created.
+     * NO-OP for this implementation.
      */
     @Override
     public void refresh() {
-        ServerAuthConfig serverAuthConfig = this.serverAuthConfig;
-        if (serverAuthConfig != null) {
-            serverAuthConfig.refresh();
-        }
+        // NO-OP
     }
 }
diff --git a/webapps/docs/changelog.xml b/webapps/docs/changelog.xml
index a8e6a406b8..402063a588 100644
--- a/webapps/docs/changelog.xml
+++ b/webapps/docs/changelog.xml
@@ -310,6 +310,12 @@
         Ensure resources are evicted from the static resource cache in the
         correct order. (markt)
       </fix>
+      <fix>
+        When Jakarta Authentication is configured for a web application, cache
+        the <code>ServerAuthConfig</code> in the <code>Authenticator</code>
+        valve. This ensures web application specific settings are cached on a
+        per web application basis. (markt)
+      </fix>
     </changelog>
   </subsection>
   <subsection name="Coyote">


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to