Pregunta

How do you configure RestTemplate from Spring 4.0.3.RELEASE with Apache httpclient 4.3.2? I've followed the code from SO here, and here, and even from Apache here, and it seems pretty straightforward, yet it has never worked for me. I can verify that the Authorization header is correctly sent when I use curl and postman, but the Authorization header is never sent with the following code:

public RestTemplate createBasicAuthTemplate(String username, String password) {
    BasicCredentialsProvider credentialsProvider = new BasicCredentialsProvider();
    credentialsProvider.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials(username, password));
    HttpClient httpClient = HttpClientBuilder.create()
            .setDefaultCredentialsProvider(credentialsProvider)
            .build();

    ClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(httpClient);
    RestTemplate template = new RestTemplate(requestFactory);

    return template;
}

and the code is called like so:

RestTemplate basicAuth = createBasicAuthTemplate("user@app.com", "password");
ResponseEntity<String> response = basicAuth.getForEntity(url, String.class);

So the questions are: How do you configure RestTemplate from Spring 4.0.3.RELEASE with Apache httpclient 4.3.2? Are there other pieces that the above code is missing? In the above code is the RestTemplate using the correct method?

¿Fue útil?

Solución

The astute reader may have noticed that the Authorization header is never sent, and realized the problem. You just have to know that it is a standard protocol to send an unauthorized request, receive a 401 with a WWW-Authenticate header, and make the request again with the Authorization header (I did not know that, so this was a great learning experience).

The rest template does not send the Authentication header on the initial request (by default it is reactive rather than proactive), so if the service does not respond with a WWW-Authenticate header (as it should according to the HTTP spec) and the RestTemplate does not attempt to send the credentials after the initial response, then the call will simply fail on the intial 401 response.

Fortunately we can tell the rest template to send the credentials on the initial request rather than waiting for a 401 with a WWW-Authenticate header.

Here is the code to do this. The trick here is to override the request factory’s createHttpContext() method to take control over the HTTP context, and use this factory in constructing the RestTemplate. This code works, and uses the self-signed certificate. You may of course restructure it to your taste…

public class BasicRequestFactory extends HttpComponentsClientHttpRequestFactory {

public BasicRequestFactory(HttpClient httpClient) {
    super(httpClient);
}

@Override
protected HttpContext createHttpContext(HttpMethod httpMethod, URI uri) {
    HttpHost targetHost = new HttpHost(uri.getHost(), uri.getPort(), uri.getScheme());
    AuthCache authCache = new BasicAuthCache();
    BasicScheme basicAuth = new BasicScheme();
    authCache.put(targetHost, basicAuth);
    BasicHttpContext localContext = new BasicHttpContext();
    localContext.setAttribute(ClientContext.AUTH_CACHE, authCache);
    return localContext;
}

private static HttpClient createSecureClient() throws NoSuchAlgorithmException, KeyStoreException, KeyManagementException {
    SSLContext sslContext = SSLContexts.custom().loadTrustMaterial(null, new TrustSelfSignedStrategy()).useTLS().build();
    SSLConnectionSocketFactory connectionFactory = new SSLConnectionSocketFactory(sslContext, new AllowAllHostnameVerifier());
    return HttpClientBuilder.create().setSSLSocketFactory(connectionFactory).build();
}

private static HttpClient createSecureClient(String username, String password) throws NoSuchAlgorithmException, KeyStoreException, KeyManagementException {
    BasicCredentialsProvider credentialsProvider = new BasicCredentialsProvider();
    credentialsProvider.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials(username, password));
    SSLContext sslContext = SSLContexts.custom().loadTrustMaterial(null, new TrustSelfSignedStrategy()).useTLS().build();
    SSLConnectionSocketFactory connectionFactory = new SSLConnectionSocketFactory(sslContext, new AllowAllHostnameVerifier());
    return HttpClientBuilder.create().setSSLSocketFactory(connectionFactory).setDefaultCredentialsProvider(credentialsProvider).build();
}

public static RestTemplate createTemplate(String username, String password) throws NoSuchAlgorithmException, KeyStoreException, KeyManagementException {
    RestTemplate template = new RestTemplate(new BasicRequestFactory(createSecureClient(username, password)));
    template.setErrorHandler(new NopResponseErrorHandler());
    return template;
}

public static RestTemplate createTemplate() throws NoSuchAlgorithmException, KeyStoreException, KeyManagementException {
    RestTemplate template = new RestTemplate(new BasicRequestFactory(createSecureClient()));
    template.setErrorHandler(new NopResponseErrorHandler());
    return template;
}

private static class NopResponseErrorHandler implements ResponseErrorHandler {

    @Override
    public boolean hasError(ClientHttpResponse chr) throws IOException {
        return false;
    }

    @Override
    public void handleError(ClientHttpResponse chr) throws IOException {
    }
}

}
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top