This is an automated email from the ASF dual-hosted git repository.
markt-asf pushed a commit to branch 10.1.x
in repository https://gitbox.apache.org/repos/asf/tomcat.git
The following commit(s) were added to refs/heads/10.1.x by this push:
new f62c65768f Refactor JASPIC caching
f62c65768f is described below
commit f62c65768fdaa300e22e64ffa7b5118dce0571a1
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 58ff079dae..9ffe839264 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);
@@ -1339,20 +1315,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);
+ }
}
}
}
@@ -1427,40 +1406,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 3676d6de03..5cb04ed9e4 100644
--- a/webapps/docs/changelog.xml
+++ b/webapps/docs/changelog.xml
@@ -171,6 +171,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]