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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/24609751/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-08 06:48:47  来源:igfitidea点击:

WebServiceTemplate with Basic Auth using HttpComponentsMessageSender

springweb-servicesauthenticationspring-securityspring-ws

提问by Going Bananas

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

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

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.CommonsHttpMessageSenderobject bean with org.apache.commons.httpclient.UsernamePasswordCredentialsand, although the client works, the code has a warning highlighted saying that the CommonsHttpMessageSenderclass is now deprecated and that I should be using HttpComponentsMessageSenderinstead.

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

I have tried re-configuring the client's WebServiceTemplateto work using the newer HttpComponentsMessageSenderclass, but I am unable to have the basic auth part configured correctly with it. For the new HttpComponentsMessageSenderclass, I have created credentials using the org.apache.http.auth.UsernamePasswordCredentialsclass 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?

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

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

我的工作代码与旧的已弃用类使用的罐子: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.

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

提前致谢,下午

回答by vasekt

Use HttpComponentsMessageSenderwith UsernamePasswordCredentials. Note that HttpComponentsMessageSendermust be created as Spring bean or you must call afterPropertiesSetmanually to be http client correctlly set up. This works for me:

HttpComponentsMessageSender与 一起使用UsernamePasswordCredentials。请注意,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;
    }

}

回答by hasto

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

这是我们项目的锻炼org.apache.httpcomponentshttpclient-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

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

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

回答by Daniel Seidewitz

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.

我使用的一种解决方案是使用自定义 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);
    }
}

回答by Prashant Kataria

Thread is old but to summaries.

线程是旧的,但要总结。

As per spring documentation:

根据 spring 文档:

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

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

回答by Going Bananas

In the end, to make Basic Authentication work with the Spring WebServiceTemplatein spring-ws-xxx.2.2.0.RELEASEusing 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.

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

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

我仍然不确定这是否是配置 Spring 的最佳方式,WebServiceTemplate但这是我发现(到目前为止)无需直接访问HttpClient'sHttpClientContext对象即可启用抢占式身份验证的唯一方式。任何更简单更好的答案我都非常欢迎......

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