Skip to content

Integrating Eclipse Soteria with Jetty EE10+ #14165

@lrasku

Description

@lrasku

Jetty version(s)
Jetty 12.1.5

Jetty Environment
ee10, probably ee11

HTTP version
HTTP 1.1, likely others

Java version/vendor (use: java -version)
openjdk version "21.0.9" 2025-10-21 LTS
OpenJDK Runtime Environment (Red_Hat-21.0.9.0.10-1) (build 21.0.9+10-LTS)
OpenJDK 64-Bit Server VM (Red_Hat-21.0.9.0.10-1) (build 21.0.9+10-LTS, mixed mode, sharing)

OS type/version
AlmaLinux 9.6

Description
I'm having trouble integrating Eclipse Soteria 3.0.4 with embedded Jetty in EE10+ environments. So far I've gotten HttpServletRequest.authenticate calls to go through to Soteria-providede BasicAuthenticationMechanism (with a custom JaspiAuthenticator subclass that sets "jakarta.security.auth.message.MessagePolicy.isMandatory" on the message map), but then they fail with a NPE:

java.lang.NullPointerException: Cannot invoke "jakarta.servlet.http.HttpServletResponse.setHeader(String, String)" because "response" is null
	at org.glassfish.soteria.mechanisms.BasicAuthenticationMechanism.validateRequest(BasicAuthenticationMechanism.java:77)
	at org.glassfish.soteria.mechanisms.BasicAuthenticationMechanism$Proxy$_$$_WeldClientProxy.validateRequest(Unknown Source)
	at org.glassfish.soteria.mechanisms.jaspic.HttpBridgeServerAuthModule.validateRequest(HttpBridgeServerAuthModule.java:89)
	at org.eclipse.jetty.ee10.security.jaspi.provider.SimpleServerAuthContext.validateRequest(SimpleServerAuthContext.java:45)
	at org.eclipse.jetty.ee10.security.jaspi.JaspiAuthenticator.validateRequest(JaspiAuthenticator.java:175)
	at example.CustomJaspiAuthenticator.validateRequest(CustomJaspiAuthenticator.java:36)
	at org.eclipse.jetty.security.internal.DeferredAuthenticationState.authenticate(DeferredAuthenticationState.java:52)
	at org.eclipse.jetty.ee10.servlet.ServletApiRequest.getUndeferredAuthenticationState(ServletApiRequest.java:280)
	at org.eclipse.jetty.ee10.servlet.ServletApiRequest.getUserPrincipal(ServletApiRequest.java:515)
	at org.eclipse.jetty.ee10.servlet.ServletApiRequest.authenticate(ServletApiRequest.java:617)
	at example.TestServlet.doGet(TestServlet.java:96)
	at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:527)
	at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:614)
	at org.eclipse.jetty.ee10.servlet.ServletHolder.handle(ServletHolder.java:752)
	at org.eclipse.jetty.ee10.servlet.ServletHandler$ChainEnd.doFilter(ServletHandler.java:1620)
	at org.eclipse.jetty.ee10.servlet.ServletHandler$MappedServlet.handle(ServletHandler.java:1554)
	at org.eclipse.jetty.ee10.servlet.ServletChannel.dispatch(ServletChannel.java:868)
	at org.eclipse.jetty.ee10.servlet.ServletChannel.handle(ServletChannel.java:449)
	at org.eclipse.jetty.ee10.servlet.ServletHandler.handle(ServletHandler.java:469)
	at org.eclipse.jetty.security.SecurityHandler.handle(SecurityHandler.java:546)
	at org.eclipse.jetty.ee10.servlet.SessionHandler.handle(SessionHandler.java:719)
	at org.eclipse.jetty.server.handler.ContextHandler.handle(ContextHandler.java:1224)
	at org.eclipse.jetty.server.Server.handle(Server.java:197)
	at org.eclipse.jetty.server.internal.HttpChannelState$HandlerInvoker.run(HttpChannelState.java:720)
	at org.eclipse.jetty.server.internal.HttpConnection.onFillable(HttpConnection.java:412)
	at org.eclipse.jetty.server.internal.HttpConnection$FillableCallback.succeeded(HttpConnection.java:1810)
	at org.eclipse.jetty.io.FillInterest.fillable(FillInterest.java:105)
	at org.eclipse.jetty.io.SelectableChannelEndPoint$1.run(SelectableChannelEndPoint.java:54)
	at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:1009)
	at org.eclipse.jetty.util.thread.QueuedThreadPool$Runner.doRunJob(QueuedThreadPool.java:1239)
	at org.eclipse.jetty.util.thread.QueuedThreadPool$Runner.run(QueuedThreadPool.java:1194)
	at java.base/java.lang.Thread.run(Thread.java:1583)

What seems to happen here is that:

  1. ServletApiRequest.authenticate is called.
  2. As its first action, the above function tries to determine if a user is already authenticated by calling ServletApiRequest.getUserPrincipal.
  3. That function, in turn, tries to resolve the deferred authentication state, but using an overload that takes only the current request object - not the response one.
  4. DeferredAuthenticationState.authenticate thus has to substitute in its dummy Response __deferredResponse to the underlying authenticator.
  5. And when JaspiMessageInfo.getResponseMessage later tries to unwrap that into a HttpServletResponse-compatible class, it fails and returns null.

In EE9 this does not happen, because there the equivalent __deferredResponse object implements HttpServletResponse, which can be successfully unwrapped for BasicAuthenticationMechanism.validateRequest to process.

Am I or Soteria doing something wrong, or is the EE10 deferred auth implementation faulty?

How to reproduce?
jetty-soteria-integration.zip
contains the simplest Maven project I could build to reproduce this issue. mvn package builds a uber-jar target/jetty-soteria-integration-0.0.0.jar that, when run, listens on localhost:9000. Navigating to http://localhost:9000/test attemps basic auth, which will crash with the above stack trace.

Metadata

Metadata

Labels

BugFor general bugs on Jetty sideSecurity

Type

No type

Projects

Status

No status

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions