1

I use Spring security to authenticate my spring websocket server. It works fine with the Basic authentication, but it went wrong when I changed to Digest authentication. I don't know what to put into the headers. Does someone know any solutions?

This is the websocket client code snippet:

SockJsClient sockJsClient;
WebSocketStompClient stompClient;
List<Transport> transports = new ArrayList<>();
final WebSocketHttpHeaders headers = new WebSocketHttpHeaders();
headers.add("Authorization", "Basic YWRtaW46YWRtaW4=");
transports.add(new WebSocketTransport(new StandardWebSocketClient()));
sockJsClient = new SockJsClient(transports);

stompClient = new WebSocketStompClient(sockJsClient);
stompClient.setMessageConverter(new MappingJackson2MessageConverter());
......

Update:

It works well with digest for rest, the following codes can configure RestTempalte to use digest:

import java.net.URI;
import org.apache.http.HttpHost;
import org.apache.http.client.AuthCache;
import org.apache.http.client.HttpClient;
import org.apache.http.client.protocol.ClientContext;
import org.apache.http.impl.auth.DigestScheme;
import org.apache.http.impl.client.BasicAuthCache;
import org.apache.http.protocol.BasicHttpContext;
import org.apache.http.protocol.HttpContext;
import org.springframework.http.HttpMethod;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;

public class HttpComponentsClientHttpRequestFactoryDigestAuth extends HttpComponentsClientHttpRequestFactory {

    public HttpComponentsClientHttpRequestFactoryDigestAuth(HttpClient client) {
        super(client);
    }

    @Override
    protected HttpContext createHttpContext(HttpMethod httpMethod, URI uri) {
        return createHttpContext(uri);
    }

    private HttpContext createHttpContext(URI uri) {
        // Create AuthCache instance
        AuthCache authCache = new BasicAuthCache();
        // Generate DIGEST scheme object, initialize it and add it to the local auth cache
        DigestScheme digestAuth = new DigestScheme();
        // If we already know the realm name
        digestAuth.overrideParamter("realm", "myrealm");
        HttpHost targetHost = new HttpHost(uri.getHost(), uri.getPort());
        authCache.put(targetHost, digestAuth);

        // Add AuthCache to the execution context
        BasicHttpContext localcontext = new BasicHttpContext();
        localcontext.setAttribute(ClientContext.AUTH_CACHE, authCache);
        return localcontext;
    }
}

Get a rest template:

import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;

public class RestTempleteConfig {

    public RestTemplate getRestTemplate() {
        CloseableHttpClient client = HttpClientBuilder.create().setDefaultCredentialsProvider(provider())
                .useSystemProperties().build();
        HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactoryDigestAuth(
                client);

        return new RestTemplate(requestFactory);
    }

    private CredentialsProvider provider() {
        CredentialsProvider provider = new BasicCredentialsProvider();
        UsernamePasswordCredentials credentials = new UsernamePasswordCredentials("admin", "admin");
        provider.setCredentials(AuthScope.ANY, credentials);
        return provider;
    }
}

Use rest template:

RestTemplate restTemplate = new RestTempleteConfig().getRestTemplate();
String uri = "http://localhost:8080/login";
ResponseEntity<String> entity = restTemplate.exchange(uri, HttpMethod.GET, null, String.class);
System.out.println(entity.getBody());
Dave Pateral
  • 1,415
  • 1
  • 14
  • 21
  • 1
    What do you know about Digest algorithm? It is not just setting a header. – holmis83 Mar 06 '17 at 12:15
  • When a client sends a request to server, it will receive a 401 error for the first time. Then the client can get realm and nonce from the response header to send a subsequent request. I think it may be ok if I send the correct header but it's hard to get the nonce. But what's the right way to authenticate websocket with digest? – Dave Pateral Mar 07 '17 at 01:41

1 Answers1

1

I have figured out the solution. Configure digest authentication with spring security at the sever side, then change client implementation to this:

RestTemplate restTemplate = new RestTempleteConfig().getRestTemplate();

SockJsClient sockJsClient;
WebSocketStompClient stompClient;
List<Transport> transports = new ArrayList<>();
final WebSocketHttpHeaders headers = new WebSocketHttpHeaders();

StandardWebSocketClient websocketClient = new StandardWebSocketClient();
// add restTemplate first
transports.add(new RestTemplateXhrTransport(restTemplate));
transports.add(new WebSocketTransport(websocketClient));
sockJsClient = new SockJsClient(transports);

stompClient = new WebSocketStompClient(sockJsClient);
stompClient.setMessageConverter(new MappingJackson2MessageConverter());

The digest is configured in the rest template, what we need to do is to add it to Transport list. And you should add rest template first, then websocket, for it matters when creating the sockJs url. More details refer to this link.

Community
  • 1
  • 1
Dave Pateral
  • 1,415
  • 1
  • 14
  • 21