Search code examples
spring-securityspring-websocketdigest-authentication

spring websocket with digest authentication


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());

Solution

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