12

I need to be able to abort a websocket connection during the handshake in case the HTTP request does not meet certain criteria. From what I understand, the proper place to do that is inside the ServerEndpointConfig.Configurator.modifyHandshake() method of my own Configurator implementation. I just can't figure out what to do to abort the connection. There's a HandshakeResponse parameter which allows adding headers to the response but I couldn't find any header that does the job.

So how can I abort a websocket connection during the handshake? Is that even possible?

Célio
  • 563
  • 7
  • 16

4 Answers4

3

you're right , use ´modifyHandShake()´ to update the response headers , you need exactly to remove or set the value of the header Sec-WebSocket-Accept, check this from the spec

The |Sec-WebSocket-Accept| header field indicates whether the server is willing to accept the connection. If present, this header field must include a hash of the client's nonce sent in |Sec-WebSocket-Key| along with a predefined GUID. Any other value must not be interpreted as an acceptance of the connection by the server.

    HTTP/1.1 101 Switching Protocols
    Upgrade: websocket
    Connection: Upgrade
    Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=

These fields are checked by the WebSocket client for scripted pages. If the |Sec-WebSocket-Accept| value does not match the expected value, if the header field is missing, or if the HTTP status code is not 101, the connection will not be established, and WebSocket frames will not be sent.

your code would look like this :

 @Override
public void modifyHandshake(ServerEndpointConfig sec,
    HandshakeRequest request, HandshakeResponse response) {
    super.modifyHandshake(sec, request, response);
    response.getHeaders().put(HandshakeResponse.SEC_WEBSOCKET_ACCEPT, new ArrayList<String>());
}

The browser will interpret this like server did not accepted the connection. for example in chrome I get the message

Error during Websocket handshake

Community
  • 1
  • 1
Leo
  • 1,829
  • 4
  • 27
  • 51
  • this should work, but just for the sake of cleaner implementation I would suggest implementing servlet filter and check the request there.. – Pavel Bucek Feb 14 '14 at 07:38
  • 2
    Hi @Leo, I have already tried this approach before but it doesn't work (at least on Tomcat). It seems to me that the server code sets the `Sec-WebSocket-Accept` header after the call to `modifyHandshake()`. When printing the response headers inside `modifyHandshake()` like this: `logger.info("response.getHeaders() => " + response.getHeaders());` We get this: `response.getHeaders() => {}` – Célio Feb 14 '14 at 10:56
  • well , I tested it with wildfly , which uses undertow, I suppose it may be an implementation detail. – Leo Feb 14 '14 at 15:39
  • It looks like a bug when modifyHandshake modifies headers, but an implementation of Tomcat ignores them. Same problem TJWS showed, so I connected to the maintainer and he told that the problem fixed for version 118. – user2305886 Apr 29 '22 at 02:21
  • This doesn't work for me unfortunately (Jetty 9.4.51). I have used the modifyHandshake method and set an empty value for Sec-WebSocket-Accept as suggested, but Jetty completes the WebSocket setup after that, and populates Sec-WebSocket-Accept with a successful value anyway. – David Ellis May 31 '23 at 11:22
3

I know - old thread but I don't see any improvements about this. So maybe someone can discuss my solution for this problem.

I tried leos version, running Wildfly 8.0 , Undertow 1.0

final  ArrayList<String> finalEmpty = new ArrayList<String>();
response.getHeaders().put(HandshakeResponse.SEC_WEBSOCKET_ACCEPT,finalEmpty);

This will cause some funny behavior:

Even though your browser should close the connection, they will all go through the onOpen() procedure.

  • Chrome: Will trigger onOpen(), then triggers onError(). In my case, I do some logging about disconnected clients so I anways call onClose() when there is an error.
  • IE: Will act like Chrome
  • Firefox: Firefox will just run the onOpen() procedure. And won't trigger onError(). So your Server doesn't even know the client is disconnected.

Don't mess up your headers and don't let the client do the dity work.

Instead, you should add authentification data to Configuration.

/** some verification foo code...**/

config.getUserProperties().put("isValid",true);

in onOpen() you then check for the value of isValid. If it isnt, you call onClose(session,null);. and the session will be closed.

It's not the best solution but thats because websocket authentification sucks and every browser is acting different sometimes. Please see: Websocket: Closing browser triggers onError() in chrome but onClose() event in Firefox

Community
  • 1
  • 1
Lama
  • 2,886
  • 6
  • 43
  • 59
  • 1
    Problem with this is that you're then a bit late: The WebSocket connection has been established, and you need to close it again. It should be possible to say "no, sorry, access denied" already with the HTTP Handshake. – stolsvik Mar 22 '20 at 14:06
  • But if setting "Sec-WebSocket-Accept" value to empty string is not working on Tomcat. What can I do? It must be on Tomcat. – HS1 Apr 27 '20 at 12:09
  • config.getUserProperties() is shared across endpoints and also not thread safe – Ashwin Prabhu Jul 19 '22 at 15:37
2

The easiest way is to setup simple web-filter in your web.xml which will be executed before handshake.

Smth like:

public class MyHandshakeFilter implements Filter {

@Override
public void init(FilterConfig config) throws ServletException {
    // do nothing
}

/**
 * Passes request to chain when request port is equal to specified in the allowedPort property
 * and returns HttpServletResponse.SC_NOT_FOUND in other case.
 */
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException,
        ServletException {

    if (<your validation here based on http request>) {
        // request conditions are met. continue handshake...
        chain.doFilter(request, response);
    } else {
        // request conditions are NOT met. reject handshake...
        ((HttpServletResponse) response).setStatus(HttpServletResponse.SC_FORBIDDEN);
    }
}
}

and in web.xml:

<filter>
    <filter-name>yourHandshakeFilter</filter-name>
    <filter-class>com....MyHandshakeFilter</filter-class>
</filter>
<filter-mapping>
    <filter-name>yourHandshakeFilter</filter-name>
    <url-pattern>/yourEndpointUrl</url-pattern>
</filter-mapping>

and your ServerEndpoint should be smth like:

@ServerEndpoint("/yourEndpointUrl")
public class MyServerEndpoint {
...
}
walv
  • 2,680
  • 3
  • 31
  • 36
  • Unfortunately, it doesn't work for me (on Jetty 9.4) – Vladimir Petrakovich Jun 16 '17 at 04:27
  • Filters shall actually not run on a HTTP Upgrade: https://stackoverflow.com/a/25996042/39334 "In practice you should work with the expectation that upgrades occur before the Servlet processing (and this includes filters), as this is how the spec is written. There are open bugs against the spec about how interactions with filters and whatnot should be treated, but those are currently unanswered and loosely scheduled for a future version of the javax.websocket spec." – stolsvik Jan 08 '20 at 21:12
  • I didn't find any reliable source telling that a filter will intercept websocket connection. So I do not consider your solution portable unless you could prove the opposite. – user2305886 Apr 28 '22 at 23:15
2

Another technique:

When you throw a RuntimeException from ServerEndpointConfig.Configurator#modifyHandshake then the connection is not established.

This works in Tomcat 8. Got the idea from a Jetty example so I guess it also works in Jetty.

wero
  • 32,544
  • 3
  • 59
  • 84
  • Yeah, this works in Jetty 9.4, but it logs a heavy WARN line with the full Exception - not ideal at all. Also, on the client it triggers onerror then onclose with CloseCode 1006 CLOSED_ABNORMALLY - at least in Firefox. However, that close code can come for several other scenarios too. There should really be an explicit "ACCESS DENIED" close code, that could be triggered from the HTTP Handshake. – stolsvik Mar 22 '20 at 14:05
  • I use the same approach with TJWS and it works perfect. – user2305886 Apr 28 '22 at 23:17