使用HttpComponentsMessageSender的WebServiceTemplate进行基本身份验证

14

我正在尝试测试一个使用基本身份验证保护的Spring Web服务。为了进行这些测试,我编写了一个Web服务客户端,使用了Spring的WebServiceTemplate类。

当我将模板的MessageSender创建为一个org.springframework.ws.transport.http.CommonsHttpMessageSender对象bean,并使用org.apache.commons.httpclient.UsernamePasswordCredentials时,我的Web服务客户端调用到Web服务是可以正常工作的,尽管客户端可以工作,但代码有一个警告,指出CommonsHttpMessageSender类现在已经被弃用,我应该使用HttpComponentsMessageSender替代。

我尝试重新配置客户端的WebServiceTemplate以使用更新的HttpComponentsMessageSender类,但我无法正确配置基本认证部分。对于新的HttpComponentsMessageSender类,我使用org.apache.http.auth.UsernamePasswordCredentials类创建凭据,但是当我调用Web服务时,凭据似乎无法随请求一起使用?是否有任何使用这些新类进行身份验证请求的WebServiceTemplate客户端的工作示例呢?

我的旧代码与已弃用的类一起使用的jar文件:commons-httpclient-3.1spring-ws-core-2.2.0.RELEASE

我的新代码与较新的类一起使用的jar文件:httpclient-4.3.4httpcore-4.3.2spring-ws-core-2.2.0.RELEASE

目前,我的非工作代码测试配置如下:

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;
    }

}

提前致谢, PM


请发布您的配置。我们使用相同的设置没有问题,所以我怀疑您的设置有误。 - M. Deinum
嗨@M.Deinum,我已经添加了Web服务客户端的Spring Beans配置代码,因为当前代码无法正常工作。我的客户端然后自动装配模板,并简单地调用Object response = webServiceTemplate.marshalSendAndReceive(request);其中请求只是Web服务的有效负载POJO。 - Going Bananas
1
你可能遇到了https://dev59.com/8mEi5IYBdhLWcg3w0fFR。我以为我们使用的是同一个堆栈,但实际上我们使用的是4.2版本而不是4.3版本。 - M. Deinum
是的,这基本上就是我遇到的问题。然而,经过查看,他们直接使用HttpClient类来设置一个带有添加了AuthCacheHttpClientContext,并在调用服务时使用它。是否有示例展示如何为Spring的WebServiceTemplate设置AuthCache - Going Bananas
啊,我已经注意到了区别。我们没有配置 HttpClient,而是在 HttpComponentsMessageSender 上设置了凭据,它会处理其余的事情。你可能想要看一下 HttpComponentsMessageSender 的源代码。 - M. Deinum
5个回答

14
使用HttpComponentsMessageSenderUsernamePasswordCredentials。注意,HttpComponentsMessageSender 必须作为Spring bean创建,或者你必须手动调用afterPropertiesSet来正确设置http客户端。以下是可行的代码:
@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;
    }

}

8
这对我没用。我托管了一个模拟服务,但我没有收到任何特定的头部信息。 - Jafar Ali

8

这是我们项目中使用的org.apache.httpcomponents的训练:httpclient-4.5.3httpcore-4.4.6

我们创建了拦截器头RequestDefaultHeaders reqHeader = new RequestDefaultHeaders(headers),然后在构建CloseableHttpClient时使用.addInterceptorLast(reqHeader)添加到httpClient中。

配置类:

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;
}

3

我使用的一个解决方案是创建一个带有自定义凭据提供程序的定制WebServiceMessageSender。此解决方案还设置了一条路线规划器,尊重默认的java代理设置。

@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);
    }
}

-1
最终,为了让基本身份验证与Spring WebServiceTemplatespring-ws-xxx.2.2.0.RELEASE版本中使用当前的httpclient-4.3.+httpcore-4.3.+类一起工作,我已经向HttpClient添加了一个预先认证拦截器(正如@Oliv在Preemptive Basic authentication with Apache HttpClient 4中建议的那样)。请注意,正如@Oliv所指出的,此解决方案会向所有请求添加身份验证。
我仍然不确定这是否是配置Spring WebServiceTemplate的最佳方法,但这是我找到的唯一一种启用预先认证而无需直接访问HttpClientHttpClientContext对象的方法。如果有更简单更好的答案,我非常欢迎...
拦截器代码:
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);
        }
    }
}

不确定您所说的“没有直接访问HttpClient的HttpClientContext对象”的含义,或者为什么您不想要它。为什么不只是装饰HttpClient并在每个执行调用中传递自己更改的上下文呢? - kulatamicuda

-1

虽然这个线程有点老,但是总结一下:

根据Spring文档:

UsernamePasswordCredentials和HttpComponentsMessageSender应该是Spring beans。因此,定义bean并注入它们即可解决问题。


提供所引用文档的链接将非常有帮助。 - dtrunk

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接