使用 Spring-WS 将基本授权 HTTP Headers 添加到 SOAP 请求
Add Basic Authorization HTTP Headers to SOAP Request with Spring-WS
我一直在尝试使用 Spring 应用程序来使用 SOAP 服务,但我完全被卡住了一段时间。我希望解决方案会非常简单,我希望能指出正确的方向。
我能够通过 SoapUI 成功发出请求。我只是加载了 .WSDL 文件,选择了要使用的方法,并从 SoapUI 的 'Auth' 菜单中添加了具有基本授权的用户名和密码。
在 Spring 应用程序中,我已经到了遇到 401 错误的地步,所以我相信它几乎就在那里。我参考了下面这两个很好的示例,首先添加用户名和密码,然后记录 HTTP headers 以验证它们是否正确填充。
https://codenotfound.com/spring-ws-log-client-server-http-headers.html
然而,无论是设置'usernamePasswordCredentials',还是设置连接的请求header似乎都没有任何效果。我还通过在 SoapUI 中测试记录的输出来确认 XML body 是正确的。所以我认为这只是一个授权问题。
Bean/Configuration Class:
@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;
}
Bean 方法:
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;
}
拦截器:
@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;
}
结果片段(这是我希望看到 username/password header 的地方。因为它们不见了,我猜这就是问题所在):
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)
跟进对我有用的解决方案。这一次,我考虑通过回调函数来解决这个问题。这让我想到了这里提出的问题:
Add SoapHeader to org.springframework.ws.WebServiceMessage
Pranav Kumar 的第二个回答对我有用。所以我干脆在'GetTicketDetailsResponse getTicketDetails(long definitionId, long itemId)'方法中的'marshalSendAndReceive'函数调用中添加了回调函数。通过这种方式,我能够添加授权 header,使我克服了之前遇到的 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 ...==");
}
});
在此添加之后,授权 header 出现在请求中并且响应已成功返回。
我一直在尝试使用 Spring 应用程序来使用 SOAP 服务,但我完全被卡住了一段时间。我希望解决方案会非常简单,我希望能指出正确的方向。
我能够通过 SoapUI 成功发出请求。我只是加载了 .WSDL 文件,选择了要使用的方法,并从 SoapUI 的 'Auth' 菜单中添加了具有基本授权的用户名和密码。
在 Spring 应用程序中,我已经到了遇到 401 错误的地步,所以我相信它几乎就在那里。我参考了下面这两个很好的示例,首先添加用户名和密码,然后记录 HTTP headers 以验证它们是否正确填充。
https://codenotfound.com/spring-ws-log-client-server-http-headers.html
然而,无论是设置'usernamePasswordCredentials',还是设置连接的请求header似乎都没有任何效果。我还通过在 SoapUI 中测试记录的输出来确认 XML body 是正确的。所以我认为这只是一个授权问题。
Bean/Configuration Class:
@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;
}
Bean 方法:
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;
}
拦截器:
@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;
}
结果片段(这是我希望看到 username/password header 的地方。因为它们不见了,我猜这就是问题所在):
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)
跟进对我有用的解决方案。这一次,我考虑通过回调函数来解决这个问题。这让我想到了这里提出的问题: Add SoapHeader to org.springframework.ws.WebServiceMessage
Pranav Kumar 的第二个回答对我有用。所以我干脆在'GetTicketDetailsResponse getTicketDetails(long definitionId, long itemId)'方法中的'marshalSendAndReceive'函数调用中添加了回调函数。通过这种方式,我能够添加授权 header,使我克服了之前遇到的 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 ...==");
}
});
在此添加之后,授权 header 出现在请求中并且响应已成功返回。