Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Credentials are ignored for websocket connections. #13565

Open
defnull opened this issue Nov 30, 2020 · 10 comments
Open

Credentials are ignored for websocket connections. #13565

defnull opened this issue Nov 30, 2020 · 10 comments
Labels
area/security kind/bug Something isn't working

Comments

@defnull
Copy link

defnull commented Nov 30, 2020

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
@defnull defnull added the kind/bug Something isn't working label Nov 30, 2020
@ghost ghost added the area/security label Nov 30, 2020
@ghost
Copy link

ghost commented Nov 30, 2020

/cc @sberyozkin

@sberyozkin
Copy link
Member

@defnull but WebSockets work over the upgraded channel where no Authorization header is available, right ?

@defnull
Copy link
Author

defnull commented Nov 30, 2020

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.

@defnull
Copy link
Author

defnull commented Nov 30, 2020

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.

@defnull
Copy link
Author

defnull commented Nov 30, 2020

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.

@defnull defnull changed the title Credentials and security annotations are ignored on websocket endpoints. Credentials are ignored for websocket connections. Nov 30, 2020
@SIMULATAN
Copy link
Contributor

SIMULATAN commented Jun 6, 2022

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...

@narddog40
Copy link

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

@mkamneng
Copy link

Hi Guys,

I have being trying for a while now to spike using quarkus/oidc/keycloak idendenty propagation for websockets using

  1. native quarkus websocket
  2. 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

@mkamneng
Copy link

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.

@flowt-au
Copy link

@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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area/security kind/bug Something isn't working
Projects
None yet
Development

No branches or pull requests

6 participants