quarkus
quarkus copied to clipboard
Credentials are ignored for websocket connections.
Describe the bug
There is currently no way (I know of) to protect a @ServerEndpoint with standard basic auth. The Authorization header is ignored and the Session.getUserPrincipal() property is not populated, which contradicts the "Proactive Authentication" principle described in quarkus docs.
Security annotations like @RolesAllowed are applied and work as expected (they prevent the annotated methods from being called and throw io.quarkus.security.UnauthorizedException) but they will always fail because no principal is present. It's a bit unintuitive that these errors do not close the websocket connection, but that's not required by the spec I guess.
Expected behavior
If an Authorization header is present in the initial websocket handshake and basic auth is enabled as a feature, the handshake request should be authenticated as usual. Bad credentials should abort the handshake and not start a websocket session.
Actual behavior
The Authorization header is completely ignored for websocket connections.
To Reproduce
Take an example project with working basic auth and add this:
@ApplicationScoped
@ServerEndpoint("/ws")
@RolesAllowed("no-one")
public class WSEndpoint {
private static final Logger LOG = LoggerFactory.getLogger(WSEndpoint.class);
@OnOpen
public void onOpen(Session session) {
LOG.info("principal = {}", session.getUserPrincipal()); // will always be null
}
@OnClose
public void onClose(Session session) {
LOG.info("close");
}
@OnError
public void onError(Session session, Throwable throwable) {
LOG.warn("error", throwable);
}
@OnMessage
public void onMessage(Session session, String message) {
LOG.info("text: {}", message);
}
}
Now try to connect with a websocket client with or without Authorization headers set. Browsers understand ws://test:test@localhost:8080/ws URIs. In both cases, with or without credentials, an io.quarkus.security.UnauthorizedException will be logged and the connection will be closed abruptly (no end frame). If the @RolesAllowed is on a method, the session is created but then fails when calling the annotated method. In that case, the connection is not closed.
Configuration
quarkus.http.auth.basic=true
Environment (please complete the following information):
- Output of
java -version: 14.0.2+12-Ubuntu-120.04 - Quarkus version or git rev: 1.10.1.Final
/cc @sberyozkin
@defnull but WebSockets work over the upgraded channel where no Authorization header is available, right ?
They work with or without Authorization header, with or without @RolesAllowed. Both are ignored. There is an UnauthorizedException error in the logs if @RolesAllowed is used but the websocket still works as if no annotation were present.
Okay, more details: If @RolesAllowed is on the class, then the websocket handshake is accepted, but the socket is then closed abnormally (code 1006, no end frame) because of UT026001: Unable to instantiate endpoint: java.lang.RuntimeException: io.quarkus.security.UnauthorizedException, no matter if the credentials are present, right or wrong.
If @RolesAllowed is on the @OnOpen or other method, then the same exception (UnauthorizedException) is triggered when that method is called, and sent to the @OnError method afterwards as you'd expect. But this time the connection is not closed and the client can continue sending messages. That's probably because a failing @OnOpen call does not close the socket. The principal is still null, even if correct credentials are provided.
The security annotations probably work as intended, but they always fail because the principal is never authenticated for websockets. The UnauthorizedException is then handled like a normal application error during initialisation or method calls, depending on where the annotation was applied. You could argue that an error during @OnOpen should close the socket, but that's not required by the spec I think. Not ideal, but also not really a bug.
So the only actual problem is the missing authentication during the websocket handshake if credentials are provided. I'll edit the issue description.
That said, perhaps this is now a feature request an not longer a bug. I'm not sure.
This is still an issue in June of 2022 - Making a Websocket Endpoint with @Authenticated and then connecting to it without the Authentication header set causes the server to throw an UnauthorizedException, but it keeps the connection open for some reason. This error can't even be caught in the onError handler...
I concur with @SIMULATAN , I'm seeing the same issue. I'm using quarkus 2.7.5 final. I too removed the authorization header and couldn't catch the error in the onError section either...further my debug wouldn't allow me to breakpoint anywhere in the onError (maybe a localized thing?). If the JWT is bad, of course quarkus is smart enough to not even allow the websocket establish a connection, but it appears this is not the case for a missing Authentication header - sessions appear to be "let" through and the quarkus pod throws these kinda errors silently (see below):
2022-06-10 06:24:12,761 ERROR [org.jbo.thr.errors] (executor-thread-28) Thread Thread[executor-thread-28,5,main] threw an uncaught exception: java.lang.RuntimeException: io.quarkus.security.UnauthorizedException
at io.undertow.websockets.ServerWebSocketContainer.invokeEndpointMethod(ServerWebSocketContainer.java:534)
at io.undertow.websockets.ServerWebSocketContainer$6.run(ServerWebSocketContainer.java:514)
at io.quarkus.vertx.core.runtime.VertxCoreRecorder$13.runWith(VertxCoreRecorder.java:543)
at org.jboss.threads.EnhancedQueueExecutor$Task.run(EnhancedQueueExecutor.java:2449)
at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.run(EnhancedQueueExecutor.java:1478)
at org.jboss.threads.DelegatingRunnable.run(DelegatingRunnable.java:29)
at org.jboss.threads.ThreadLocalResettingRunnable.run(ThreadLocalResettingRunnable.java:29)
at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
at java.base/java.lang.Thread.run(Unknown Source)
Caused by: io.quarkus.security.UnauthorizedException
at io.quarkus.security.runtime.interceptor.check.AuthenticatedCheck.doApply(AuthenticatedCheck.java:38)
at io.quarkus.security.runtime.interceptor.check.AuthenticatedCheck.apply(AuthenticatedCheck.java:25)
at io.quarkus.security.runtime.interceptor.SecurityConstrainer.check(SecurityConstrainer.java:32)
at io.quarkus.security.runtime.interceptor.SecurityHandler.handle(SecurityHandler.java:46)
at io.quarkus.security.runtime.interceptor.AuthenticatedInterceptor.intercept(AuthenticatedInterceptor.java:29)
at io.quarkus.security.runtime.interceptor.AuthenticatedInterceptor_Bean.intercept(Unknown Source)
at io.quarkus.arc.impl.InterceptorInvocation.invoke(InterceptorInvocation.java:41)
at io.quarkus.arc.impl.AroundInvokeInvocationContext.perform(AroundInvokeInvocationContext.java:41)
at io.quarkus.arc.impl.InvocationContexts.performAroundInvoke(InvocationContexts.java:32)
at com.xxxx.xxxx.xx.micro.xxxxxxx.subscription.SubscriptionWebsocketEndpoint_Subclass.onError(Unknown Source)
at com.xxxx.xxxx.xx.micro.xxxxxxx.subscription.SubscriptionWebsocketEndpoint_ClientProxy.onError(Unknown Source)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
at java.base/java.lang.reflect.Method.invoke(Unknown Source)
at io.undertow.websockets.annotated.BoundMethod.invoke(BoundMethod.java:87)
at io.undertow.websockets.annotated.AnnotatedEndpoint$5.run(AnnotatedEndpoint.java:225)
at io.undertow.websockets.ServerWebSocketContainer$1.call(ServerWebSocketContainer.java:143)
at io.undertow.websockets.ServerWebSocketContainer$1.call(ServerWebSocketContainer.java:140)
at io.quarkus.websockets.client.runtime.WebsocketCoreRecorder$4$1.call(WebsocketCoreRecorder.java:181)
at io.undertow.websockets.ServerWebSocketContainer.invokeEndpointMethod(ServerWebSocketContainer.java:532)
... 8 more
Hi Guys,
I have being trying for a while now to spike using quarkus/oidc/keycloak idendenty propagation for websockets using
- native quarkus websocket
- vertx sockjs eventbus
I have not being able so far to get any of them working using bearer token.
Had anyone being able to get this working? Any status on this ticket?
Thank you
For anyone insterested, as work arround for now (vertx sockjs eventbus); I'm overriding the io.quarkus.vertx.web.runtime.RouteHandler.handle(RoutingContext context) whithin each user session and keeping the ManagedContext active for the lifespan of the session.
@mkamneng I am trying to get WebSocket security working and have posted a question on SO here: https://stackoverflow.com/questions/75141521/quarkus-how-to-secure-websocket-when-using-keycloak-oidc
So far no luck. I was wondering whether you might be able to offer some guidance?
Thank you, Murray