使用 HttpComponentsMessageSender 的具有基本身份验证的 WebServiceTemplate

Posted

技术标签:

【中文标题】使用 HttpComponentsMessageSender 的具有基本身份验证的 WebServiceTemplate【英文标题】:WebServiceTemplate with Basic Auth using HttpComponentsMessageSender 【发布时间】:2014-08-27 21:15:38 【问题描述】:

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

当我使用 org.apache.commons.httpclient.UsernamePasswordCredentials 将模板的 MessageSender 创建为 org.springframework.ws.transport.http.CommonsHttpMessageSender 对象 bean 时,我的 Web 服务客户端调用 Web 服务正常工作,尽管客户端工作,但代码有一个突出显示的警告说 CommonsHttpMessageSender类现在已弃用,我应该改用HttpComponentsMessageSender

我已尝试重新配置客户端的 WebServiceTemplate 以使用较新的 HttpComponentsMessageSender 类工作,但我无法正确配置基本身份验证部分。对于新的HttpComponentsMessageSender 类,我已经使用org.apache.http.auth.UsernamePasswordCredentials 类创建了凭据,但是,当我调用Web 服务时,请求中的凭据似乎不可用?是否有任何使用这些新类来验证请求等的 WebServiceTemplate 客户端的工作示例?

我的旧类不推荐使用的工作代码使用的罐子:commons-httpclient-3.1spring-ws-core-2.2.0.RELEASE

我的带有较新类的非工作代码使用的罐子: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;
    


提前致谢, 下午

【问题讨论】:

发布您的配置。我们使用相同的设置没有问题,所以我怀疑你的设置有误。 嗨@M.Deinum,我已经添加了Web 服务客户端的spring beans 配置代码,因为它目前代表非工作代码。然后我的客户端在模板中自动装配并简单地调用Object response = webServiceTemplate.marshalSendAndReceive(request);,其中请求只是 Web 服务的有效负载 POJO。 您可能遇到了***.com/questions/20914311/…。我以为我们在哪里使用相同的堆栈,但是我们使用的是 4.2 版而不是 4.3 版。 是的,这就是我遇到的问题。但是,看过之后,他们直接使用HttpClient 类来设置HttpClientContext,并在调用服务时添加AuthCache。是否有示例显示如何设置 AuthCache 以与 Spring 的 WebServiceTemplate 一起使用? 啊,我已经注意到了不同之处。我们没有配置HttpClient,而只是在HttpComponentsMessageSender 上设置了凭据,它负责其余的工作。您可能想看看HttpComponentsMessageSender 的来源。 【参考方案1】:

最后,为了使用当前的 httpclient-4.3.+httpcore-4.3.+ 类在 spring-ws-xxx.2.2.0.RELEASE 中使用 Spring WebServiceTemplate 进行基本身份验证,我在 HttpClient 中添加了一个抢占式身份验证拦截器(如建议@Oliv 在Preemptive Basic authentication with Apache HttpClient 4)。请注意,正如@Oliv 所指出的,此解决方案会向所有发出的请求添加身份验证。

我仍然不确定这是否是配置 Spring WebServiceTemplate 的最佳方式,但这是我(到目前为止)在不直接访问 HttpClient 的 @987654329 的情况下启用抢占式身份验证的唯一方式@ 目的。我非常欢迎任何更简单更好的答案...

拦截器代码:

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 并在每个执行调用中传递自己更改的上下文?【参考方案2】:

我使用的一个解决方案是使用自定义 CredentialsProvider 创建自定义 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);
    

【讨论】:

【参考方案3】:

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;
    


【讨论】:

这对我不起作用。我托管了一个模拟服务,但没有收到任何此类特定的标头。【参考方案4】:

线程是旧的,但总结。

根据春季文档:

UsernamePasswordCredentials 和 HttpComponentsMessageSender 应该是 spring bean。所以定义bean并注入它们。它应该可以解决问题。

【讨论】:

参考文档的链接会很有帮助【参考方案5】:

这是我们使用 org.apache.httpcomponents 的项目的练习: httpclient-4.5.3, httpcore-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;

【讨论】:

以上是关于使用 HttpComponentsMessageSender 的具有基本身份验证的 WebServiceTemplate的主要内容,如果未能解决你的问题,请参考以下文章

在使用加载数据流步骤的猪中,使用(使用 PigStorage)和不使用它有啥区别?

今目标使用教程 今目标任务使用篇

Qt静态编译时使用OpenSSL有三种方式(不使用,动态使用,静态使用,默认是动态使用)

MySQL db 在按日期排序时使用“使用位置;使用临时;使用文件排序”

使用“使用严格”作为“使用强”的备份

Kettle java脚本组件的使用说明(简单使用升级使用)