使用 Spring-WS 将基本授权 HTTP 标头添加到 SOAP 请求

use*_*193 3 spring soap spring-ws spring-boot

我一直在尝试使用 Spring 应用程序使用 SOAP 服务,但我已经被完全卡住了一段时间。我希望解决方案将非常简单,并且希望能够指出正确的方向。

我能够通过 SoapUI 成功发出请求。我只需加载 .WSDL 文件,选择要使用的方法,并从 SoapUI 中的“Auth”菜单添加具有基本授权的用户名和密码。

在 Spring 应用程序中,我已经遇到了 401 错误,所以我相信它已经差不多了。我引用了下面这两个很好的示例,首先添加用户名和密码,然后记录 HTTP 标头以验证它们是否正确填充。

https://codenotfound.com/spring-ws-log-client-server-http-headers.html

使用 Spring-WS 客户端动态设置自定义 HTTP 标头

但是,设置“usernamePasswordCredentials”和设置连接的请求标头似乎都没有任何效果。我还通过测试 SoapUI 中记录的输出来确认 XML 正文是正确的。所以我认为这只是一个授权问题。

Bean/配置类:

@Bean
public Jaxb2Marshaller marshaller() {
    System.out.println("BEAN CREATED: MARSHALLER...");
    Jaxb2Marshaller marshaller = new Jaxb2Marshaller();
    marshaller.setContextPath("..."); // omitted for example

    return marshaller;
}

@Bean
public UsernamePasswordCredentials usernamePasswordCredentials() {
    return new UsernamePasswordCredentials("testu", "test");
}

@Bean
@DependsOn({"usernamePasswordCredentials"})
public HttpComponentsMessageSender httpComponentsMessageSender(UsernamePasswordCredentials usernamePasswordCredentials) {
    HttpComponentsMessageSender httpComponentsMessageSender = new HttpComponentsMessageSender();
    
    httpComponentsMessageSender.setCredentials(usernamePasswordCredentials);

    return httpComponentsMessageSender;
}

@Bean
@DependsOn({"marshaller"})
public TicketDetailsClient ticketDetailsClient(Jaxb2Marshaller marshaller) {
    System.out.println("BEAN CREATED: TICKETDETAILSCLIENT...");
    TicketDetailsClient ticketDetailsClient = new TicketDetailsClient();
    
    ticketDetailsClient.setDefaultUri("..."); // omitted for example
    ticketDetailsClient.setMarshaller(marshaller);
    ticketDetailsClient.setUnmarshaller(marshaller);

    return ticketDetailsClient;
}
Run Code Online (Sandbox Code Playgroud)

豆法:

    public GetTicketDetailsResponse getTicketDetails(long definitionId, long itemId) {
    ObjectFactory of = new ObjectFactory();

    this.template.setInterceptors(new ClientInterceptor[] {new LogHttpHeaderClientInterceptor()});
    
    GetItemDetailsRequest itemDetailsRequest = new GetItemDetailsRequest();
    itemDetailsRequest.setItemDefinitionId(definitionId);
    itemDetailsRequest.setItemId(itemId);
    
    GetTicketDetails getTicketDetails = new GetTicketDetails();
    getTicketDetails.setGetItemDetailsRequest(itemDetailsRequest);
    JAXBElement<GetTicketDetails> elGetTicketDetails = of.createGetTicketDetails(getTicketDetails);
    
    System.out.println("ABOUT TO MARSHALSENDANDRECEIVE...");
    GetTicketDetailsResponse ticketDetailsResponse = (GetTicketDetailsResponse) this.template.marshalSendAndReceive(elGetTicketDetails);
    
    return ticketDetailsResponse;
}
Run Code Online (Sandbox Code Playgroud)

拦截器:

@Override
public boolean handleRequest(MessageContext arg0) throws WebServiceClientException {
    // TODO Auto-generated method stub
    TransportContext context = TransportContextHolder.getTransportContext();
    HttpComponentsConnection connection =(HttpComponentsConnection) context.getConnection();
    try {
        connection.addRequestHeader("username", "testu");
        connection.addRequestHeader("password", "test");
    } catch (IOException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }

    HttpLoggingUtils.logMessage("Client Request Message", arg0.getRequest());

    return true;
}
Run Code Online (Sandbox Code Playgroud)

结果片段(这是我期望看到用户名/密码标头的地方。由于它们丢失了,我猜这就是问题所在):

ABOUT TO MARSHALSENDANDRECEIVE...
2021-08-09 13:46:18.891  INFO 23112 --- [           main] com.fp.fpcustomization.HttpLoggingUtils  : 
----------------------------
Client Request Message
----------------------------
Accept: text/xml, text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2
SOAPAction: ""
Content-Type: text/xml; charset=utf-8
Content-Length: 378
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"><SOAP-ENV:Header/><SOAP-ENV:Body><ns3:getTicketDetails xmlns:ns3="http://externalapi.business.footprints.numarasoftware.com/"><getItemDetailsRequest><_itemDefinitionId>76894</_itemDefinitionId><_itemId>30201</_itemId></getItemDetailsRequest></ns3:getTicketDetails></SOAP-ENV:Body></SOAP-ENV:Envelope>

[ERROR] Tests run: 1, Failures: 0, Errors: 1, Skipped: 0, Time elapsed: 2.151 s <<< FAILURE! - in com.fp.fpcustomization.FpcustomizationApplicationTests
[ERROR] RequestTest  Time elapsed: 0.51 s  <<< ERROR!
org.springframework.ws.client.WebServiceTransportException:  [401]
    at com.fp.fpcustomization.FpcustomizationApplicationTests.RequestTest(FpcustomizationApplicationTests.java:29)
Run Code Online (Sandbox Code Playgroud)

use*_*193 5

跟进对我有用的解决方案。这一次,我考虑通过回调函数来解决这个问题。这让我想到了这里提出的问题: Add SoapHeader to org.springframework.ws.WebServiceMessage

Pranav Kumar 的第二个答案对我有用。因此,我只是将回调函数添加到“GetTicketDetailsResponse getTicketDetails(longDefinitionId, long itemId)”方法中的“marshalSendAndReceive”函数调用中。这样,我就能够添加授权标头,从而克服之前遇到的 401 错误。

Object theResponseObject = this.template.marshalSendAndReceive((elGetTicketDetails), new WebServiceMessageCallback(){
            @Override
            public void doWithMessage(WebServiceMessage message) throws IOException, TransformerException {
                SaajSoapMessage soapMessage = (SaajSoapMessage) message;
                MimeHeaders mimeHeader = soapMessage.getSaajMessage().getMimeHeaders();
                mimeHeader.setHeader("Authorization", "Basic ...==");
            }
        });
Run Code Online (Sandbox Code Playgroud)

添加此内容后,授权标头将显示在请求中,并且响应已成功返回。