1

I need to create a Websocket Server using spring which was easily completed by using the below code

 @Configuration
 @EnableWebSocket
 public class WebSocketConfig implements WebSocketConfigurer {
 @Override
 public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
 registry.addHandler(new SocketHandler(), "/poll");
 registry.addHandler(new SocketPushHandler(), "/push");
 }

 }

Where SocketHandler and SocketPushHandler are the handler classes for the websocket endpoints.

Till this very thing is good able to run the server and connect to the endpoint using normal socket = new WebSocket(websocketUrl) javascript code.

Now we need to have OAuth 2.0 Security implemented on the API endpoints which is done easily by importing some Spring security dependencies.\

Now the hard part is writing the client to connect the secure endpoint by passing the Oauth Authorization beaer <token> as part of header. From documents came to know that we can't send headers to Web-socket endpoints.

So according to the information form this Is it possible to secure WebSocket APIs with OAuth 2.0? link I created a API gateway /open-ws Request type GET to which the client would connect and send the authorization headers and this endpoint internally on server-side will open an WebSocket Client connection passing the the headers as javax.websocket.WebSocketContainer supports websocket client with custom headers.

So now my javascript first makes an GET ajax call to the Gateway endpoint and on success makes an new websocket request.

Below is the Spring API mimicking as gateway

@RequestMapping(value = "/open-ws", method = RequestMethod.GET)
public void getOperatorTokenDefinition(@RequestHeader(value = HttpHeaders.AUTHORIZATION) String bearerToken,
                @RequestHeader(value = "websocketURL") String websocketURL,
                HttpServletRequest acquireTokenServletRequest, HttpServletResponse response) {

    webSocketClient.connecttoserver(websocketURL, acquireTokenServletRequest.getRemoteHost(), bearerToken, response);
    // ResponseEntity<String> responseEntity = new ResponseEntity<>("connected", HttpStatus.OK);
     }

}

Below is my Spring side client.

    @Component
public class WebSocketClient {

    private Session client;

public void connecttoserver(String websocketURL,String host,String bearerAccessToken, HttpServletResponse response) {

    final AtomicReference<String> message = new AtomicReference<>();
    Endpoint endpoint = new Endpoint() {
        @Override
        public void onOpen(Session session, EndpointConfig config) {
             System.out.println("WSS OPEN!!!!!");
  try (OutputStream output = response.getOutputStream()) {
            output.write(session.getId());
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }


         }
    };

    ClientEndpointConfig.Configurator configurator = new ClientEndpointConfig.Configurator() {

        @Override
        public void beforeRequest(Map<String, List<String>> headers) {

            List<String> connection = new ArrayList<>(1);
            connection.add("Upgrade");
            List<String> origin = new ArrayList<>(1);
            origin.add(originURL);
            List<String> upgradeWebsocket = new ArrayList<>(1);
            upgradeWebsocket.add("WebSocket");
            List<String> host = new ArrayList<>(1);
            host.add(websocketURL);
            List<String> contenttype = new ArrayList<>(1);
            contenttype.add("application/json");
            List<String> authorization = new ArrayList<>(1);
            authorization.add("Bearer " + bearerAccessToken);
            List<String> tenantId = new ArrayList<>(1);
            tenantId.add(tenantID);
            List<String> key = new ArrayList<>(1);
            key.add("HcFOxrSD89ya65X2qMF9lQ==");
            List<String> version = new ArrayList<>(1);
            version.add("13");

            headers.put("Connection", connection);
            headers.put("Upgrade", upgradeWebsocket);
            headers.put("Host", host);
            headers.put("Origin", origin);
            // headers.put("Content-Type", contenttype);
            headers.put("Authorization", authorization);
            headers.put("Sec-WebSocket-Key", key);
            headers.put("Sec-WebSocket-Version", version);
        }

    };


    ClientEndpointConfig clientConfig = ClientEndpointConfig.Builder.create().configurator(configurator).build();

    WebSocketContainer container = ContainerProvider.getWebSocketContainer();
    try {

        // if (!this.client.isOpen())
        this.client = container.connectToServer(endpoint, clientConfig, URI.create(websocketURL));


        client.addMessageHandler(new MessageHandler.Whole<String>() {

            @Override
            public void onMessage(String response) {
                // TODO Auto-generated method stub
                message.set(response);
                // System.out.println("response>>>>>>>>>>>>>>>>>>> "+response.toString());// this dosent work
            }
        });

        System.out.println("Response--------------------------------->" + message.get());
        // client.close();

    } catch (DeploymentException | IOException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
        }

    }
}

Below is the JQuery code

      $(document).ready(function(){
        $("#Websocketconnect").click(function(){
            $.ajax({
                type: 'GET',
                url: hostUrl.value,
                headers: {
                "Authorization":websocketToken.value,
                "websocketURL":websocketUrl.value,
                "Content-type":"text/plain"
                },
                success: function(result){
                $("#readystatus").value = result;
                webSocketCycleEvent(websocketUrl.value);
            }});
        });
    });


    function webSocketCycleEvent(websocketUrl){

      socket = new WebSocket(websocketUrl);
      socket.onerror = function(error) {
        console.log('WebSocket Error: ' + error);
      };


      // Show a connected message when the WebSocket is opened.
      socket.onopen = function(event) {
        socketStatus.innerHTML = 'Connected to: ' + websocketUrl;
        socketStatus.className = 'open';
      };
      socket.onmessage = function(event) {
        var message = event.data;
        messagesList.innerHTML += '<li class="received"><span>Received:</span>' +
                                   message + '</li>';
      };

   socket.onclose = function(event) {
        socketStatus.innerHTML = 'Disconnected from WebSocket.';
        socketStatus.className = 'closed';
      };

      }

      form.onsubmit = function(e) {
          e.preventDefault();

        // Retrieve the message from the textarea.
        var message = messageField.value;
        socket.send(message);
        messagesList.innerHTML += '<li class="sent"><span>Sent:</span>' + message +
                                  '</li>';

        // Clear out the message field.
        messageField.value = '';
        return false;
      };

I am not to connect to the websocket and send the socket.id as the server-side gives an

javax.websocket.DeploymentException: The HTTP response from the server [400] did not permit the HTTP upgrade to WebSocket
    at org.apache.tomcat.websocket.WsWebSocketContainer.connectToServer(WsWebSocketContainer.java:343)
    at com.example.simplewebsocketserver.WebSocketClient.connecttoserver(WebSocketClient.java:101)
    at com.example.simplewebsocketserver.WebSocketController.getOperatorTokenDefinition(WebSocketController.java:31)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)

So the Question arises is this the proper way of connecting the Oauth 2.0 Websocket, if yes how to deal with the above error, if no how to send headers to the authorization endpoint.

Note: No using Stomp because we are not yet confirmed whether the actual client i.e UI will allow/have stomp JS.

Gautam Naik
  • 109
  • 2
  • 11

0 Answers0