13

I am trying to test a Spring Web Service which is currently secured with Basic Authentication underneath. For these tests, I have written a Web Service client using Spring's WebServiceTemplate class.

My Web Service client calls to the Web Service work okay when I create the template's MessageSender as a org.springframework.ws.transport.http.CommonsHttpMessageSender object bean with org.apache.commons.httpclient.UsernamePasswordCredentials and, although the client works, the code has a warning highlighted saying that the CommonsHttpMessageSender class is now deprecated and that I should be using HttpComponentsMessageSender instead.

I have tried re-configuring the client's WebServiceTemplate to work using the newer HttpComponentsMessageSender class, but I am unable to have the basic auth part configured correctly with it. For the new HttpComponentsMessageSender class, I have created credentials using the org.apache.http.auth.UsernamePasswordCredentials class but, when I make a call to the Web Service, the credentials seem to not be available with the request? Is there a working example of a WebServiceTemplate client anywhere that uses these newer classes for authenticating requests, etc?

Jars that my working code with old deprecated classes uses: commons-httpclient-3.1, spring-ws-core-2.2.0.RELEASE.

Jars that my NON-working code with newer classes uses: httpclient-4.3.4, httpcore-4.3.2, spring-ws-core-2.2.0.RELEASE.

Test Configuration as it stands for NON-working code:

package com.company.service.a.ws.test.config;

import java.io.IOException;

import org.apache.http.HttpException;
import org.apache.http.HttpRequest;
import org.apache.http.HttpRequestInterceptor;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.client.HttpClient;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.protocol.HTTP;
import org.apache.http.protocol.HttpContext;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.context.annotation.PropertySource;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
import org.springframework.oxm.jaxb.Jaxb2Marshaller;
import org.springframework.ws.client.core.WebServiceTemplate;
import org.springframework.ws.soap.saaj.SaajSoapMessageFactory;
import org.springframework.ws.transport.http.HttpComponentsMessageSender;

@PropertySource("classpath:/${environment}-use-case-data.properties")
@ComponentScan(basePackages = "com.company.service.a.ws.test")
@Configuration
public class TestConfig {

    @Value("${ws.url}")
    private String wsUrl;

    @Value("${ws.username}")
    private String username;

    @Value("${ws.password}")
    private String password;

    private static final Logger logger = LogManager.getLogger();

    @Bean
    public SaajSoapMessageFactory messageFactory() {
        return new SaajSoapMessageFactory();
    }

    @Bean
    public Jaxb2Marshaller marshaller() {
        Jaxb2Marshaller marshaller = new Jaxb2Marshaller();
        marshaller.setContextPath("com.company.service.a.ws.model.data");
        return marshaller;
    }

    @Bean RequestConfig requestConfig() {

        RequestConfig requestConfig = RequestConfig.custom()
                .setAuthenticationEnabled(true)
                .build();
        return requestConfig;
    }

    @Bean
    @DependsOn( value = "propertyConfigurer" )
    public UsernamePasswordCredentials credentials() {

        logger.debug("creating credentials for username: {} passowrd={}", 
                username, password);

        UsernamePasswordCredentials credentials = new UsernamePasswordCredentials(
                username, password);

        return credentials;
    }

    @Bean 
    public CredentialsProvider credentialsProvider() {
        CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
        credentialsProvider.setCredentials(AuthScope.ANY, credentials());
        return credentialsProvider;
    }

    private static class ContentLengthHeaderRemover implements HttpRequestInterceptor{

        @Override
        public void process(HttpRequest request, HttpContext context) 
                throws HttpException, IOException {

            // fighting org.apache.http.protocol.RequestContent's 
            // ProtocolException("Content-Length header already present");
            request.removeHeaders(HTTP.CONTENT_LEN);
        }
    }

    @Bean
    public HttpComponentsMessageSender messageSender() {

        RequestConfig requestConfig = RequestConfig.custom()
                .setAuthenticationEnabled(true)
                .build();

        HttpClientBuilder httpClientBuilder = HttpClients.custom();

        HttpClient httpClient = httpClientBuilder
                .addInterceptorFirst(new ContentLengthHeaderRemover())
                .setDefaultRequestConfig(requestConfig)
                .setDefaultCredentialsProvider(credentialsProvider())               
                .build();

        HttpComponentsMessageSender messageSender = new HttpComponentsMessageSender(httpClient);
        return messageSender;
    }

    @Bean( name = "propertyConfigurer" )
    public static PropertySourcesPlaceholderConfigurer propertyConfigurer() {
        PropertySourcesPlaceholderConfigurer configurer = 
                new PropertySourcesPlaceholderConfigurer();

        return configurer;
    }

    @Bean
    public WebServiceTemplate webServiceTemplate() {

        logger.debug("creating webServiceTemplate to url: {}", wsUrl);

        WebServiceTemplate webServiceTemplate = new WebServiceTemplate(messageFactory());
        webServiceTemplate.setDefaultUri(wsUrl);
        webServiceTemplate.setMarshaller(marshaller());
        webServiceTemplate.setUnmarshaller(marshaller());
        webServiceTemplate.setMessageSender(messageSender());
        return webServiceTemplate;
    }

}

Thanks in advance, PM

Going Bananas
  • 2,265
  • 3
  • 43
  • 83
  • Post your configuration. We use the same setup without problems, so I suspect your setup is wrong. – M. Deinum Jul 07 '14 at 11:43
  • Hi @M.Deinum, I have added the web service client's spring beans config code as it currently stands for the non-working code. My client then autowires in the template and simply calls `Object response = webServiceTemplate.marshalSendAndReceive(request);` where the request is just the Web Service's payload POJO. – Going Bananas Jul 07 '14 at 11:59
  • 1
    You are probably running into http://stackoverflow.com/questions/20914311/httpclientbuilder-basic-auth. I thought we where using the same stack however we are using version 4.2 and not 4.3. – M. Deinum Jul 07 '14 at 12:09
  • Yes, that is pretty much the problem I am having. However, having looked at it, they are using the `HttpClient` class directly to then set a `HttpClientContext` with an `AuthCache` added to it as they call the service. Is there an example somewhere showing how to set the `AuthCache` for use with Spring's `WebServiceTemplate`? – Going Bananas Jul 07 '14 at 13:13
  • Ah I already notice the difference. We didn't configure the `HttpClient` but just set the credentials on the `HttpComponentsMessageSender` which takes care of the rest. You might want to take a peek at the sources of the `HttpComponentsMessageSender`. – M. Deinum Jul 07 '14 at 13:31

5 Answers5

14

Use HttpComponentsMessageSender with UsernamePasswordCredentials. Note that HttpComponentsMessageSender must be created as Spring bean or you must call afterPropertiesSet manually to be http client correctlly set up. This works for me:

@Configuration
public class WsClientConfiguration {


    @Bean
    public ESignatureProcessorClient eSignatureProcessorClient() {
        ESignatureProcessorClient client = new ESignatureProcessorClient();
        client.setWebServiceTemplate(mwWebServiceTemplate());
        return client;
    }

    @Bean
    public WebServiceTemplate mwWebServiceTemplate() {
        Jaxb2Marshaller marshaller = new Jaxb2Marshaller();
        marshaller.setContextPath("cz.csas.services.esignatureprocessor.v02_02");
        WebServiceTemplate template = new WebServiceTemplate(marshaller, marshaller);
        template.setDefaultUri("https://osb-st2.vs.csin.cz:5001/CSMW/WS_MW_ESignatureProcessor_v02_02");
        template.setMessageSender(defaultMwMessageSender());
        return template;
    }

    @Bean
    public HttpComponentsMessageSender defaultMwMessageSender() {
        HttpComponentsMessageSender messageSender = new HttpComponentsMessageSender();
        messageSender.setCredentials(new UsernamePasswordCredentials("user", "password"));
        return messageSender;
    }

}
vasekt
  • 2,055
  • 2
  • 17
  • 13
  • 7
    This is not working for me. I have hosted a mock service and I didn't received any such particular Header. – Jafar Ali Aug 28 '19 at 19:06
8

This is workout for our project using org.apache.httpcomponents : httpclient-4.5.3, httpcore-4.4.6

We create interceptor header RequestDefaultHeaders reqHeader = new RequestDefaultHeaders(headers) and then add to httpClient using .addInterceptorLast(reqHeader) when building CloseableHttpClient

Configuration class :

import org.apache.http.message.BasicHeader;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.Header;
import org.apache.http.client.protocol.RequestDefaultHeaders;


@Bean
HttpClient createHttpClient() {
    List<Header> headers = new ArrayList<>();
    BasicHeader authHeader = new BasicHeader("Authorization", "Basic " + base64authUserPassword());
    headers.add(authHeader);
    // add more header as more as needed

    RequestDefaultHeaders reqHeader = new RequestDefaultHeaders(headers);

    CloseableHttpClient httpClient = 
        HttpClients.custom()
            .addInterceptorFirst(new HttpComponentsMessageSender.RemoveSoapHeadersInterceptor())
            .addInterceptorLast(reqHeader)
            .build();
    return httpClient;
}

@Bean
public HttpComponentsMessageSender defaultMyMessageSender() 
        throws KeyManagementException, NoSuchAlgorithmException, KeyStoreException {

    HttpComponentsMessageSender messageSender = new HttpComponentsMessageSender(createHttpClient());
    //messageSender.setCredentials(credentials());
    return messageSender;
}

@Bean
WebServiceTemplate webServiceTemplate() throws KeyManagementException, NoSuchAlgorithmException, KeyStoreException{

    WebServiceTemplate wsTemplate = new WebServiceTemplate();
    wsTemplate.setDefaultUri(endpointURI);
    wsTemplate.setMessageSender(defaultMyMessageSender());

    return wsTemplate;
}
hasto
  • 85
  • 1
  • 3
3

One solution I have used is to create a custom WebServiceMessageSender with a custom CredentialsProvider. This solution also sets a route planner that respects the default java proxy settings.

@Configuration
public class WebServiceConfiguration {

    @Bean
    public WebServiceMessageSender webServiceMessageSender(@Value("${endpoint.uri}") endpointUri, 
                                                           @Value("${endpoint.username}") String username, 
                                                           @Value("${endpoint.password}") String password) throws Exception {
        SystemDefaultRoutePlanner routePlanner = new SystemDefaultRoutePlanner(
            ProxySelector.getDefault());
        BasicCredentialsProvider credentialsProvider = new BasicCredentialsProvider();
        credentialsProvider.setCredentials(new AuthScope(endpointUri.getHost(), endpointUri.getPort(), ANY_REALM, ANY_SCHEME), new UsernamePasswordCredentials(username, password););
        CloseableHttpClient httpclient = HttpClients.custom()
            .setRoutePlanner(routePlanner)
            .addInterceptorFirst(new HttpComponentsMessageSender.RemoveSoapHeadersInterceptor())
            .setDefaultCredentialsProvider(credentialsProvider)
            .build();

        return new HttpComponentsMessageSender(httpclient);
    }
}
Daniel Seidewitz
  • 720
  • 8
  • 23
-1

In the end, to make Basic Authentication work with the Spring WebServiceTemplate in spring-ws-xxx.2.2.0.RELEASE using current httpclient-4.3.+, httpcore-4.3.+ classes, I've added a preemptive authentication interceptor to the HttpClient (as suggested by @Oliv in Preemptive Basic authentication with Apache HttpClient 4). Note that, as pointed out by @Oliv, this solution adds authentication to ALL requests made.

I am still not sure if this is the best way to configure the Spring WebServiceTemplate but it is the only way I have found (so far) of enabling preemptive authentication without direct access to the HttpClient's HttpClientContext object. Any simpler better answers I would very much welcome...

Interceptor code:

private static class PreemptiveAuthInterceptor implements HttpRequestInterceptor {

    public void process(final HttpRequest request, final HttpContext context) 
            throws HttpException, IOException {

        AuthState authState = (AuthState) context.getAttribute(
                HttpClientContext.TARGET_AUTH_STATE);

        // If no auth scheme is avaialble yet, initialize it preemptively
        if ( authState.getAuthScheme() == null ) {

            CredentialsProvider credsProvider = (CredentialsProvider) context.getAttribute(
                    HttpClientContext.CREDS_PROVIDER);

            HttpHost targetHost = (HttpHost) context.getAttribute(
                    HttpCoreContext.HTTP_TARGET_HOST);

            Credentials creds = credsProvider.getCredentials(
                    new AuthScope(targetHost.getHostName(), targetHost.getPort()));

            if ( creds == null ) {
                throw new HttpException("no credentials available for preemptive "
                        + "authentication");
            }

            authState.update(new BasicScheme(), creds);
        }
    }
}
Community
  • 1
  • 1
Going Bananas
  • 2,265
  • 3
  • 43
  • 83
  • Not sure what you mean by "without direct access to the HttpClient's HttpClientContext object" or rather why you do not want it. Why not simply decorating HttpClient and pass own changed context in each execute call ? – kulatamicuda Aug 13 '14 at 15:33
-1

Thread is old but to summaries.

As per spring documentation:

UsernamePasswordCredentials and HttpComponentsMessageSender should be spring beans. So define beans and inject them. It should solve the problem.