SOAP security, client authentication problem - java

I tried to connect to TERYT but I get an error about the lack of authorization.
WSDL address - https://uslugaterytws1test.stat.gov.pl/wsdl/terytws1.wsdl
java.lang.IllegalStateException: Failed to execute ApplicationRunner
at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:765) [spring-boot-2.7.1.jar:2.7.1]
at org.springframework.boot.SpringApplication.callRunners(SpringApplication.java:752) [spring-boot-2.7.1.jar:2.7.1]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:315) [spring-boot-2.7.1.jar:2.7.1]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1306) [spring-boot-2.7.1.jar:2.7.1]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1295) [spring-boot-2.7.1.jar:2.7.1]
at com.example.teryt.TerytApplication.main(TerytApplication.java:15) [classes/:na]
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_332]
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_332]
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_332]
at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_332]
at org.springframework.boot.devtools.restart.RestartLauncher.run(RestartLauncher.java:49) [spring-boot-devtools-2.7.1.jar:2.7.1]
Caused by: com.sun.xml.internal.ws.fault.ServerSOAPFaultException: Client received SOAP Fault from server: An error occurred when processing the security tokens in the message. Please see the server log to find more detail regarding exact cause of the failure.
at com.sun.xml.internal.ws.fault.SOAP11Fault.getProtocolException(SOAP11Fault.java:178) ~[na:1.8.0_332]
at com.sun.xml.internal.ws.fault.SOAPFaultBuilder.createException(SOAPFaultBuilder.java:116) ~[na:1.8.0_332]
at com.sun.xml.internal.ws.client.sei.StubHandler.readResponse(StubHandler.java:238) ~[na:1.8.0_332]
at com.sun.xml.internal.ws.db.DatabindingImpl.deserializeResponse(DatabindingImpl.java:189) ~[na:1.8.0_332]
at com.sun.xml.internal.ws.db.DatabindingImpl.deserializeResponse(DatabindingImpl.java:276) ~[na:1.8.0_332]
at com.sun.xml.internal.ws.client.sei.SyncMethodHandler.invoke(SyncMethodHandler.java:104) ~[na:1.8.0_332]
at com.sun.xml.internal.ws.client.sei.SyncMethodHandler.invoke(SyncMethodHandler.java:77) ~[na:1.8.0_332]
at com.sun.xml.internal.ws.client.sei.SEIStub.invoke(SEIStub.java:147) ~[na:1.8.0_332]
at com.sun.proxy.$Proxy83.pobierzListeWojewodztw(Unknown Source) ~[na:na]
at com.example.teryt.TerytClient.getResponse(TerytClient.java:31) ~[classes/:na]
at com.example.teryt.TerytApplication.lambda$lookup$0(TerytApplication.java:20) [classes/:na]
at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:762) [spring-boot-2.7.1.jar:2.7.1]
This is what the correct query should look like:
<?xml version="1.0" encoding="UTF-8"?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="http://tempuri.org/">
soapenv:Header
<wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
<wsse:UsernameToken wsu:Id="UsernameToken-2018-01-11T00:16:02+00:00">
wsse:UsernameTestPubliczny</wsse:Username>
wsse:Password1234abcd</wsse:Password>
</wsse:UsernameToken>
</wsse:Security>
<wsa:Action xmlns:wsa="http://www.w3.org/2005/08/addressing">http://tempuri.org/ITerytWs1/PobierzListeWojewodztw</wsa:Action>
</soapenv:Header>
soapenv:Body
ns1:PobierzListeWojewodztw
ns1:DataStanu2020-05-18</ns1:DataStanu>
</ns1:PobierzListeWojewodztw>
</soapenv:Body>
</soapenv:Envelope>
TerytApplication.java:
#SpringBootApplication
public class TerytApplication {
public static void main(String[] args) {
SpringApplication.run(TerytApplication.class, args);
}
#Bean
ApplicationRunner lookup(TerytClient terytClient){
return args -> System.out.println(terytClient.getResponse());
}
}
Client.java:
#Component
public class TerytClient extends WebServiceGatewaySupport {
public Boolean getResponse() throws DatatypeConfigurationException {
GregorianCalendar cal = new GregorianCalendar();
cal.setTime(new Date());
XMLGregorianCalendar xCal = DatatypeFactory.newInstance()
.newXMLGregorianCalendar(cal);
ITerytWs1 instance = new TerytWs1().getCustom(new AddressingFeature(true));
Binding binding = ((BindingProvider) instance).getBinding();
List<Handler> handlerList = binding.getHandlerChain();
if (handlerList == null)
handlerList = new ArrayList<Handler>();
handlerList.add(new TerytHeaderHandler("TestPubliczny", "1234abcd"));
binding.setHandlerChain(handlerList);
System.out.println(instance.pobierzListeWojewodztw(xCal));
return instance.czyZalogowany();
}
HeaderHandler.java
#Override
public boolean handleMessage(SOAPMessageContext smc) {
Boolean outboundProperty = (Boolean) smc.get(MessageContext.MESSAGE_OUTBOUND_PROPERTY);
if (outboundProperty.booleanValue()) {
try {
SOAPEnvelope envelope = smc.getMessage().getSOAPPart().getEnvelope();
SOAPHeader header = envelope.getHeader();
SOAPElement security = header.addChildElement("Security", "wsse",
"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd");
SOAPElement usernameToken = security.addChildElement("UsernameToken", "wsse");
SOAPElement username = usernameToken.addChildElement("Username", "wsse");
username.addTextNode(wsUser);
SOAPElement password = usernameToken.addChildElement("Password", "wsse");
password.setAttribute("Type",
"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText");
password.addTextNode(wsPassword);
} catch (Exception e) {
e.printStackTrace();
}
} else {
//This handler does nothing with the response from the Web Service
//even though it should probably check its mustUnderstand headers
SOAPMessage message = smc.getMessage();
}
return outboundProperty;
}
#Override
public boolean handleFault(SOAPMessageContext context) {
// TODO Auto-generated method stub
return false;
}
#Override
public void close(MessageContext context) {
// TODO Auto-generated method stub
}
// Gets the header blocks that can be processed by this Handler instance.
#Override
public Set<QName> getHeaders() {
QName securityHeader = new QName("http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd",
"Security");
HashSet<QName> headers = new HashSet<QName>();
headers.add(securityHeader);
return headers;
}
}
I would be grateful for any help and suggestions.

When building the WS-Security headers, I recommend you to use Apache WSS4J. I am attaching a code example that I use frequently.
import lombok.extern.slf4j.Slf4j;
import org.apache.wss4j.dom.WSConstants;
import org.apache.wss4j.dom.message.WSSecHeader;
import org.apache.wss4j.dom.message.WSSecTimestamp;
import org.apache.wss4j.dom.message.WSSecUsernameToken;
import org.w3c.dom.Element;
import javax.xml.namespace.QName;
import javax.xml.soap.SOAPEnvelope;
import javax.xml.soap.SOAPException;
import javax.xml.soap.SOAPHeader;
import javax.xml.soap.SOAPMessage;
import javax.xml.soap.SOAPPart;
import javax.xml.ws.handler.MessageContext;
import javax.xml.ws.handler.soap.SOAPHandler;
import javax.xml.ws.handler.soap.SOAPMessageContext;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.HashSet;
import java.util.Set;
#Slf4j
public class ClientWSSecurityUsernameTokenHandler implements
SOAPHandler<SOAPMessageContext> {
public ClientWSSecurityUsernameTokenHandler(
String userName, String password) {
_userName = userName;
_password = password;
}
#Override
public Set<QName> getHeaders() {
final String NAMESPACE_URI =
"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd";
final String LOCAL_PART = "Security";
final String PREFIX = "wsse";
final QName wsSecurity = new QName(NAMESPACE_URI, LOCAL_PART, PREFIX);
final Set<QName> headers = new HashSet<QName>();
headers.add(wsSecurity);
return headers;
}
#Override
public boolean handleMessage(SOAPMessageContext soapMessageContext) {
Boolean outboundProperty = (Boolean) soapMessageContext.get(
MessageContext.MESSAGE_OUTBOUND_PROPERTY);
if (outboundProperty) {
try {
SOAPMessage soapMessage = soapMessageContext.getMessage();
SOAPPart soapPart = soapMessage.getSOAPPart();
SOAPHeader soapHeader = soapMessage.getSOAPHeader();
if (null == soapHeader) {
SOAPEnvelope soapEnvelope = soapPart.getEnvelope();
soapHeader = soapEnvelope.addHeader();
}
WSSecHeader wsSecHeader = new WSSecHeader(soapPart);
Element securityElement =
wsSecHeader.insertSecurityHeader();
_addUsernamePassword(soapMessageContext, soapPart, wsSecHeader);
WSSecTimestamp wsSecTimeStamp = new WSSecTimestamp(wsSecHeader);
wsSecTimeStamp.build();
_appendSecurityHeader(soapHeader, securityElement);
_logSOAPMessage(soapMessageContext.getMessage());
}
catch (Exception exception) {
log.error(exception.getMessage(), exception);
}
}
return true;
}
private String getPassword() {
return _password;
}
private String getUserName() {
return _userName;
}
#Override
public boolean handleFault(SOAPMessageContext soapMessageContext) {
_logSOAPMessage(soapMessageContext.getMessage());
return true;
}
#Override
public void close(MessageContext messageContext) {
}
private String _userName;
private String _password;
private void _appendSecurityHeader(
SOAPHeader soapHeader, Element securityElement) {
soapHeader.removeChild(securityElement);
soapHeader.appendChild(securityElement);
}
private void _addUsernamePassword(
SOAPMessageContext soapMessageContext, SOAPPart soapPart,
WSSecHeader wsSecHeader) {
log.debug("Adding Username Token for the user: {}", getUserName());
WSSecUsernameToken usernameToken = new WSSecUsernameToken(wsSecHeader);
usernameToken.setUserInfo(getUserName(), getPassword());
usernameToken.setPasswordType(WSConstants.PASSWORD_TEXT);
usernameToken.addCreated();
usernameToken.addNonce();
usernameToken.build();
}
private void _logSOAPMessage(SOAPMessage message) {
if (log.isDebugEnabled()) {
ByteArrayOutputStream bout = new ByteArrayOutputStream();
try {
message.writeTo(bout);
String msg = bout.toString("UTF-8");
log.debug("Start of SOAP Message");
log.debug(msg);
log.debug("End of SOAP Message");
}
catch (SOAPException | IOException exception) {
if (log.isErrorEnabled()) {
log.error(exception.getMessage(), exception);
}
}
}
}
}
Source Code 1 - SOAP Handler ClientWSSecurityUsernameTokenHandler
The following code instead shows how to add the handler to the chain.
ClientWSSecurityUsernameTokenHandler
clientWSSSecurityUsernameTokenHandler =
new ClientWSSecurityUsernameTokenHandler(soapClientProperties.getUsername(),
soapClientProperties.getPassword());
List<Handler> handlerChain = List.of(clientWSSSecurityUsernameTokenHandler);
((BindingProvider) serviceSOAP).getBinding().setHandlerChain(handlerChain);
Source Code 2 - Add the ClientWSSecurityUsernameTokenHandler to SOAP Handler Chain
The following code instead shows how to add the wss4j-ws-security-dom as dependency.
<dependency>
<groupId>org.apache.wss4j</groupId>
<artifactId>wss4j-ws-security-dom</artifactId>
<version>2.4.1</version>
</dependency>

Related

Spring ws SOAP endpoint responds with missing fault details

I have soap-to-soap proxy server in spring using org.springframework.ws.* Both sides have identical wsdls.
I am able to pass the success response from external service to proxy consumer however there's a problem when fault message from external service gets returned.
The problem is my proxy server removes soap detail from the original response (I have no idea why). My goal is to pass the response from external service just as it is to proxy client without shortening. Anyone could help how to avoid fault detail being deleted ? Thank you in advance.
External Server response:
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<soap:Fault>
<faultcode>soap:Server</faultcode>
<faultstring>Fault occurred while processing.</faultstring>
<detail>
<ns2:getCFUSubscriberStateFaultBusiness xmlns="example.system.messaging.common.v1.datatypes" xmlns:ns2="example.system.ot.managepcccfu.v2.datatypes">
<ns2:messageContext>
<requestId>273140800423344000</requestId>
<transactionId>8200</transactionId>
<timeLeft>10000</timeLeft>
<timestamp>2022-09-30T14:08:00</timestamp>
<user>x_turan</user>
<consumingComponent>urn:example.business.intr:SELFCARE3.</consumingComponent>
<providingService>urn:example.system.ot.managepccprovisioning.v1.service:ManagePccProvisioning</providingService>
<providingOperation>modifycontroffer</providingOperation>
</ns2:messageContext>
<ns2:messageDataBusinessException>
<errorCode>3001</errorCode>
<errorMessage>ESE Problem</errorMessage>
</ns2:messageDataBusinessException>
</ns2:getCFUSubscriberStateFaultBusiness>
</detail>
</soap:Fault>
</soap:Body>
</soap:Envelope>
Proxy Client Receives:
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
<SOAP-ENV:Header/>
<SOAP-ENV:Body>
<SOAP-ENV:Fault>
<faultcode>SOAP-ENV:Server</faultcode>
<faultstring xml:lang="en">Fault occurred while processing.</faultstring>
</SOAP-ENV:Fault>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
Endpoint:
#Endpoint
public class ServiceEndpoint {
public static final String NAMESPACE_URI="example.system.ot.managepcccfu.v2.datatypes";
#Autowired
CFUSoapClient soapClient;
#PayloadRoot(namespace = NAMESPACE_URI, localPart = "getCFUSubscriberState" )
#ResponsePayload
public GetCFUSubscriberStateResponse getCFUSubscriberState(#RequestPayload GetCFUSubscriberState request) throws GetCFUSubscriberStateFaultBusinessMessage, GetCFUSubscriberStateFaultSystemMessage {
final GetCFUSubscriberStateResponse response = soapClient.getCFUSubscriberStateResponse(request);
return response;
}
}
Soap Client:
public class CFUSoapClient extends WebServiceGatewaySupport {
public GetCFUSubscriberStateResponse getCFUSubscriberStateResponse(GetCFUSubscriberState request) throws GetCFUSubscriberStateFaultBusinessMessage {
Object response = getWebServiceTemplate().marshalSendAndReceive(request);
return (GetCFUSubscriberStateResponse) response;
}
}
Config:
#Configuration
#EnableWs
public class Config extends WsConfigurerAdapter {
#Bean
public ServletRegistrationBean messageDispatcherServlet(ApplicationContext applicationContext) {
MessageDispatcherServlet servlet = new MessageDispatcherServlet();
servlet.setApplicationContext(applicationContext);
return new ServletRegistrationBean(servlet, "/ws/CFU/*");
}
#Bean(name="CFU")
public Wsdl11Definition defaultWsdl11Definition() {
SimpleWsdl11Definition wsdl11Definition = new SimpleWsdl11Definition();
wsdl11Definition.setWsdl(new ClassPathResource("/wsdl/CFU.wsdl"));
return wsdl11Definition;
}
#Bean(name = "cfuDatatypesV2")
public XsdSchema cfuDatatypesV2() {
return new SimpleXsdSchema(
new ClassPathResource("wsdl/cfuDatatypesV2.xsd"));
}
#Bean(name = "common")
public XsdSchema common() {
return new SimpleXsdSchema(
new ClassPathResource("wsdl/common.xsd"));
}
#Bean
public Jaxb2Marshaller marshaller() {
Jaxb2Marshaller jaxb2Marshaller = new Jaxb2Marshaller();
jaxb2Marshaller.setContextPath("com.ot.cfu");
return jaxb2Marshaller;
}
#Bean
public CFUSoapClient soapClient() {
CFUSoapClient client = new CFUSoapClient();
client.setDefaultUri("http://localhost:41420/CFU");
client.setMarshaller(marshaller());
client.setUnmarshaller(marshaller());
// ClientInterceptor [] interceptors = new ClientInterceptor[]{new SoapClientInterceptor()};
// client.setInterceptors(interceptors);
return client;
}
}
After hours I managed to workaround this issue following wsdl definition and implemented custom exception classes wrapping the wsdl generated fault details:
#WebFault(name = "getCFUSubscriberStateFaultBusiness", targetNamespace = "example.system.ot.managepcccfu.v2.datatypes")
public class GetCFUSubscriberStateFaultBusinessMessage extends Exception {
private GetCFUSubscriberStateFaultBusiness getCFUSubscriberStateFaultBusiness;
public GetCFUSubscriberStateFaultBusinessMessage() {
super();
}
public GetCFUSubscriberStateFaultBusinessMessage(String message) {
super(message);
}
public GetCFUSubscriberStateFaultBusinessMessage(String message, java.lang.Throwable cause) {
super(message, cause);
}
public GetCFUSubscriberStateFaultBusinessMessage(String message, GetCFUSubscriberStateFaultBusiness getCFUSubscriberStateFaultBusiness) {
super(message);
this.getCFUSubscriberStateFaultBusiness = getCFUSubscriberStateFaultBusiness;
}
public GetCFUSubscriberStateFaultBusinessMessage(String message, GetCFUSubscriberStateFaultBusiness getCFUSubscriberStateFaultBusiness, java.lang.Throwable cause) {
super(message, cause);
this.getCFUSubscriberStateFaultBusiness = getCFUSubscriberStateFaultBusiness;
}
public GetCFUSubscriberStateFaultBusiness getFaultInfo() {
return this.getCFUSubscriberStateFaultBusiness;
}
}
#WebFault(name = "getCFUSubscriberStateFaultSystem", targetNamespace = "example.system.ot.managepcccfu.v2.datatypes")
public class GetCFUSubscriberStateFaultSystemMessage extends Exception {
private GetCFUSubscriberStateFaultSystem getCFUSubscriberStateFaultSystem;
public GetCFUSubscriberStateFaultSystemMessage() {
super();
}
public GetCFUSubscriberStateFaultSystemMessage(String message) {
super(message);
}
public GetCFUSubscriberStateFaultSystemMessage(String message, java.lang.Throwable cause) {
super(message, cause);
}
public GetCFUSubscriberStateFaultSystemMessage(String message, GetCFUSubscriberStateFaultSystem getCFUSubscriberStateFaultSystem) {
super(message);
this.getCFUSubscriberStateFaultSystem = getCFUSubscriberStateFaultSystem;
}
public GetCFUSubscriberStateFaultSystemMessage(String message, GetCFUSubscriberStateFaultSystem getCFUSubscriberStateFaultSystem, java.lang.Throwable cause) {
super(message, cause);
this.getCFUSubscriberStateFaultSystem = getCFUSubscriberStateFaultSystem;
}
public GetCFUSubscriberStateFaultSystem getFaultInfo() {
return this.getCFUSubscriberStateFaultSystem;
}
}
I filled the received soap fault detail into exception placeholder upon SoapFaultClientException based on type of detail:
public class CFUSoapClient extends WebServiceGatewaySupport {
public GetCFUSubscriberStateResponse getCFUSubscriberStateResponse(GetCFUSubscriberState request) throws GetCFUSubscriberStateFaultBusinessMessage, IOException, GetCFUSubscriberStateFaultSystemMessage {
try {
return (GetCFUSubscriberStateResponse) getWebServiceTemplate().marshalSendAndReceive(request);
} catch (SoapFaultClientException e) {
final Iterator<SoapFaultDetailElement> detailEntries = e.getSoapFault().getFaultDetail().getDetailEntries();
if (detailEntries.hasNext()) {
final SoapFaultDetailElement next = detailEntries.next();
final Source source = next.getSource();
final Object faultDetail = getWebServiceTemplate().getUnmarshaller().unmarshal(source);
if (faultDetail instanceof GetCFUSubscriberStateFaultBusiness) {
throw new GetCFUSubscriberStateFaultBusinessMessage(e.getSoapFault().getFaultStringOrReason(), (GetCFUSubscriberStateFaultBusiness) faultDetail, e.getCause());
} else if (faultDetail instanceof GetCFUSubscriberStateFaultSystem) {
throw new GetCFUSubscriberStateFaultSystemMessage(e.getSoapFault().getFaultStringOrReason(), (GetCFUSubscriberStateFaultSystem) faultDetail, e.getCause());
}
}
throw new RuntimeException("Unexpected error", e);
}
}
}
Eventually, I marshalled the details in the resolver class:
#Component
public class Resolver extends AbstractEndpointExceptionResolver {
private final Jaxb2Marshaller marshaller;
#Autowired
public Resolver(Jaxb2Marshaller marshaller) {
this.marshaller = marshaller;
}
#Override
protected boolean resolveExceptionInternal(MessageContext messageContext, Object endpoint, Exception e) {
if (e instanceof GetCFUSubscriberStateFaultBusinessMessage) {
final GetCFUSubscriberStateFaultBusinessMessage getCFUSubscriberStateFaultBusinessMessage = (GetCFUSubscriberStateFaultBusinessMessage) e;
final GetCFUSubscriberStateFaultBusiness faultInfo = getCFUSubscriberStateFaultBusinessMessage.getFaultInfo();
final Result result = createFaultDetailResult(messageContext, getCFUSubscriberStateFaultBusinessMessage);
marshaller.marshal(faultInfo, result);
return true;
} else if (e instanceof GetCFUSubscriberStateFaultSystemMessage) {
final GetCFUSubscriberStateFaultSystemMessage getCFUSubscriberStateFaultSystemMessage = (GetCFUSubscriberStateFaultSystemMessage) e;
final GetCFUSubscriberStateFaultSystem faultInfo = getCFUSubscriberStateFaultSystemMessage.getFaultInfo();
final Result result = createFaultDetailResult(messageContext, getCFUSubscriberStateFaultSystemMessage);
marshaller.marshal(faultInfo, result);
return true;
}
return false;
}
private Result createFaultDetailResult(MessageContext messageContext, Exception exception) {
final SoapMessage response = (SoapMessage) messageContext.getResponse();
final SoapBody soapBody = response.getSoapBody();
final SoapFault soapFault = soapBody.addServerOrReceiverFault(exception.getMessage(), Locale.ENGLISH);
final SoapFaultDetail faultDetail = soapFault.addFaultDetail();
return faultDetail.getResult();
}
}

Add CDATA on request's string parameter using only JAX-WS

I have a jax-ws client ganerated with CXF
The request have a string-parameter (MGRequest) that contains an xml, all work's but the generated request is like this:
<S:Body>
<ns5:MGRequest><mytag>hello</mytag></ns5:MGRequest>
</S:Body>
I need to generate the body like:
<S:Body>
<ns5:MGRequest><![CDATA[<mytag>hello</mytag>]]></ns5:MGRequest>
</S:Body>
(because i can't control the server..)
The client is like a standard jax-ws:
#WebService(name = "ServiceSoap")
#XmlSeeAlso({ ObjectFactory.class})
#SOAPBinding(parameterStyle = SOAPBinding.ParameterStyle.BARE)
public interface ServiceSoap {
#WebMethod(operationName = "ProcessMessage")
#WebResult(name = "MGResponse")
public String processMessage(
#WebParam(partName = "input", name = "MGRequest") String input);
}
And i call like this:
Service client = new Service(url);
client.setHandlerResolver(HandlerFactory.build(new LoggerHandler()));
ServiceSoap service = client.getServiceSoap();
String msgToSend = JaxbUtil.jaxbObjToString(xmlObj, false);
String response = service.processMessage(msgToSend);
I have tried adding #XmlJavaTypeAdapter(CDataAdapter.class) before #WebParam but the result was:
<S:Body>
<ns5:MGRequest><![CDATA[<mytag>hello</mytag>]]></ns5:MGRequest>
</S:Body>
Where CDataAdapter:
public class CDataAdapter extends XmlAdapter<String, String> {
#Override
public String marshal(String v) throws Exception {
return "<![CDATA[" + v + "]]>";
}
#Override
public String unmarshal(String v) throws Exception {
return v;
}
}
Any idea how to archive that?
Thanks
After a working night i've found the solution:
adding a javax.xml.ws.handler.Handler to the client like this:
client.setHandlerResolver(HandlerFactory.build(new LoggerHandler(), new CDataHandler()));
where my HandlerFactory build a Handler:
public static HandlerResolver build(final Handler... handlers) {
return new HandlerResolver() {
#Override
public List<Handler> getHandlerChain(PortInfo portInfo) {
List<Handler> handlerChain = new ArrayList<Handler>();
if (handlers != null) {
for (Handler handler : handlers) {
handlerChain.add(handler);
}
}
return handlerChain;
}
};
}
import javax.xml.namespace.QName;
import javax.xml.soap.Node;
import javax.xml.soap.SOAPMessage;
import javax.xml.ws.handler.MessageContext;
import javax.xml.ws.handler.soap.SOAPHandler;
import javax.xml.ws.handler.soap.SOAPMessageContext;
public class CDataHandler implements SOAPHandler<SOAPMessageContext> {
#Override
public void close(MessageContext context) {
}
#Override
public boolean handleMessage(SOAPMessageContext soapMessage) {
try {
SOAPMessage message = soapMessage.getMessage();
boolean isOutboundMessage = (Boolean) soapMessage
.get(MessageContext.MESSAGE_OUTBOUND_PROPERTY);
// is a request?
if (isOutboundMessage) {
// build a CDATA NODE with the text in the root tag
Node cddata = (Node) message.getSOAPPart().createCDATASection(
message.getSOAPBody().getFirstChild().getTextContent());
// add the CDATA's node at soap message
message.getSOAPBody().getFirstChild().appendChild(cddata);
// remove the text tag with the raw text that will be escaped
message.getSOAPBody().getFirstChild()
.removeChild(message.getSOAPBody().getFirstChild().getFirstChild());
}
} catch (Exception ex) {
// fail
}
return true;
}
#Override
public boolean handleFault(SOAPMessageContext soapMessage) {
return true;
}
#Override
public Set<QName> getHeaders() {
return Collections.EMPTY_SET;
}
}
This is a simple class, i had only one tag with text, but in more complex scenario you can take the necessary action navigating the DOM.

consume SOAP local web service in android

I am trying to consume data from local web service in an android emulator using ksoap2 library, I test the same app with a online webservice http://www.webservicex.net/New/Home/ServiceDetail/17 and It works,
the webService has been made it on Csharp, it has a method to return a column from a DB, call neptuno(spanish adventure works),when execute dont show the data and after a moment show the error message programmed, I change the localhost for my local IP
[WebService(Namespace = "http://testapi.idat/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
[System.Web.Script.Services.ScriptService]
public class Service : System.Web.Services.WebService
{
SqlConnection cn = new SqlConnection("data source=.;initial catalog = neptuno; integrated security=true");
public Service () {
//InitializeComponent();
}
[WebMethod]
public string HelloWorld() {
return "Hello World";
}
[WebMethod]
public DataSet mostrar(){
DataSet ds = new DataSet();
cn.Open();
String sql = "select idProducto from productos";
SqlDataAdapter da = new SqlDataAdapter(sql,cn);
da.Fill(ds);
return ds;
}
}
the service works and return the column IDProduct, the problem is call with the method, I already add the permission on manifest, and the layout is just a textview for error message and edittext multiline for show the data
package com.example.webservice;
import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.widget.EditText;
import android.widget.TextView;
import android.os.AsyncTask;
import org.ksoap2.SoapEnvelope;
import org.ksoap2.serialization.SoapObject;
import org.ksoap2.serialization.SoapPrimitive;
import org.ksoap2.serialization.SoapSerializationEnvelope;
import org.ksoap2.transport.HttpTransportSE;
public class MainActivity extends Activity {
EditText resultMultiline;
TextView message;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
resultMultiline = (EditText)findViewById(R.id.editText1);
message = (TextView) findViewById(R.id.textView);
CallWebservice webservice = new CallWebservice();
webservice.execute();
}
public class CallWebservice extends AsyncTask<Integer,Integer,Boolean>{
String resultText = "";
protected Boolean doInBackground(Integer... params){
boolean result = true;
final String SOAP_ACTION = "http://testapi.idat/mostrar";
final String NAMESPACE = "http://testapi.idat/";
final String METHOD_NAME = "mostrar";
final String URL = "http://192.168.1.45:51582/Webservice/Service.asmx?WSDL";
HttpTransportSE transport = new HttpTransportSE(URL);
SoapObject request = new SoapObject(NAMESPACE,METHOD_NAME);
SoapSerializationEnvelope envelope = new SoapSerializationEnvelope(SoapEnvelope.VER11);
envelope.dotNet = true;
envelope.setOutputSoapObject(request);
try {
transport.call(SOAP_ACTION,envelope);
SoapPrimitive respSoap = (SoapPrimitive)envelope.getResponse();
resultText = respSoap.toString();
}catch (Exception e){
result = false;
Log.d("Debug", e.getMessage().toString());
}
return result;
}
protected void onPostExecute(Boolean result){
if (result){
resultMultiline.setText(resultText);
Log.d("Debug","Web service works");
}else{
message.setText("ERROR");
}
}
}
}
Use the Following function it is working in my case this is normal Login soap call you need to add internet permission in your menifest
/*
* Vishal Mokal 05-MAR-2015
* soapCALL() function returns String Array.
* Make A sope call and returns response.
* if responce is success full then user will get responce array
* first element status = 1 if syccessfull 0 = if any exception of faliur
* second element = response srtring if successful or error message.
* */
public String[] soapCALL(RequestDetails requestDetails, String wsdlUserName, String wsdlPassword , String headerName) {
String url = requestDetails.getUrl().toString();
String nameSpace = requestDetails.getNameSpace().toString();
String methodName = requestDetails.getMethodName().toString();
String soapAction = requestDetails.getSoapAction().toString();
String[] responses = new String[2];
Element[] header = new Element[1];
// header[0] = new Element().createElement(nameSpace, "AuthenticationHeader");
header[0] = new Element().createElement(nameSpace, headerName);
try {
Element UserName = new Element().createElement(nameSpace, "UserName");
UserName.addChild(Node.TEXT, wsdlUserName);
header[0].addChild(Node.ELEMENT, UserName);
Element Password = new Element().createElement(nameSpace, "Password");
Password.addChild(Node.TEXT, wsdlPassword);
header[0].addChild(Node.ELEMENT, Password);
SoapObject request = requestDetails.getSoapObject();
SoapSerializationEnvelope soapSerilizationEnvelope = new SoapSerializationEnvelope(SoapEnvelope.VER11);
soapSerilizationEnvelope.dotNet = true;
soapSerilizationEnvelope.headerOut = header;
soapSerilizationEnvelope.setOutputSoapObject(request);
Object env = soapSerilizationEnvelope.bodyOut;
HttpTransportSE httptransport = new HttpTransportSE(url);
httptransport.call(soapAction, soapSerilizationEnvelope);
SoapPrimitive response = (SoapPrimitive) soapSerilizationEnvelope.getResponse();
responses[0] = "1";
responses[1] = response.toString();
Log.d("Respons", response.toString());
return responses;
}
catch (SocketTimeoutException e)
{
responses[0] = "0";
responses[1] = "Sorry!Unable To Connect Server Please Check Your Internet Connection Or Try After Some Time.";
return responses;
}
catch (SocketException e)
{
responses[0] = "0";
responses[1] = "Sorry!Unable To Connect Server Please Try After Some Time.";
return responses;
}
catch (Exception e) {
responses[0] = "0";
responses[1] = "Sorry!Unable To Connect Server Please Try After Some Time.";
return responses;
}
}
Following is the structure for the Requestdetail class
public class RequestDetails {
private String nameSpace;
private String url;
private String methodName;
private String SoapAction;
private SoapObject soapObject;
public RequestDetails(String nameSpace, String url, String methodName, String soapAction) {
this.nameSpace = nameSpace;
this.url = url;
this.methodName = methodName;
SoapAction = soapAction;
}
public String getNameSpace() {
return nameSpace;
}
public String getUrl() {
return url;
}
public String getMethodName() {
return methodName;
}
public String getSoapAction() {
return SoapAction;
}
public SoapObject getSoapObject() {
return soapObject;
}
public void setSoapObject(SoapObject soapObject) {
this.soapObject = soapObject;
}
}

Spring ClientInterceptor shows empty SOAP header in response

I have a SOAP service which I need to talk to. The first SOAP request receives a response with a sessionID element in the SOAP header which I need to send in the final release command.
To grab this sessionID value I plan on using a ClientInterceptor. In my implementation of WebServiceGatewaySupport I register my interceptor:
this.setInterceptors(new ClientInterceptor[] { new MyWebServiceClientInterceptor() });
My Interceptor:
public class MyWebServiceClientInterceptor implements ClientInterceptor {
public final QName SessionID_QNAME = new QName("http://xml.example.com/ws/session", "sessionID");
public boolean handleFault(MessageContext context) throws WebServiceClientException {
logger.info("Handle Fault");
return true;
}
public boolean handleResponse(MessageContext context) throws WebServiceClientException {
logger.info("Handle Response");
SoapMessage soapMessage = (SoapMessage) context.getRequest();
SoapHeader soapHeader = soapMessage.getSoapHeader();
logger.info("Response Header: " + soapHeader.getName());
Iterator<SoapHeaderElement> qn = soapHeader.examineAllHeaderElements();
while (qn.hasNext()) {
SoapElement elem = qn.next();
logger.info(elem.toString());
}
Iterator<QName> an = soapHeader.getAllAttributes();
while (an.hasNext()) {
QName elem = an.next();
logger.info(elem.toString());
}
return true;
}
public boolean handleRequest(MessageContext context) throws WebServiceClientException {
logger.info("Handle Request");
return true;
}
}
However I keep getting back an empty SOAP header. I'm running Wireshark and can clearly see the sessionID token in the returned SOAP packet so at a lost as to what's going on. Any ideas?
I'm an idiot. 'Twas a typo. I needed to call:
SoapMessage soapMessage = (SoapMessage) context.getResponse();
Not:
SoapMessage soapMessage = (SoapMessage) context.getRequest();
Full code for those wanting to set headers on requests and receive headers on responses:
ClientInterceptor to receive custom SOAP header values:
public class MyWebServiceClientInterceptor implements ClientInterceptor {
public boolean handleFault(MessageContext context) throws WebServiceClientException {
return true;
}
public boolean handleResponse(MessageContext context) throws WebServiceClientException {
SoapMessage soapMessage = (SoapMessage) context.getResponse();
SoapHeader soapHeader = soapMessage.getSoapHeader();
Iterator<SoapHeaderElement> qn = soapHeader.examineHeaderElements(MY_SESSIONID_QNAME);
while (qn.hasNext()) {
SoapElement elem = qn.next();
SoapHeaderElement headerElem = (SoapHeaderElement) elem;
if (StringUtil.validString(headerElem.getText())) {
if (!headerElem.getText().equals(sessionId)) {
sessionId = headerElem.getText();
logger.info("Bound to Session ID: " + sessionId);
}
}
}
return true;
}
public boolean handleRequest(MessageContext context) throws WebServiceClientException {
return true;
}
}
Where I use the interceptor:
public class MySoapClient extends WebServiceGatewaySupport {
public MySoapClient() {
this.setInterceptors(new ClientInterceptor[] { new MyWebServiceClientInterceptor() });
...
}
...
}
Callback to set custom SOAP Header:
class MySoapActionCallback implements WebServiceMessageCallback {
public void doWithMessage(WebServiceMessage message) throws IOException, TransformerException {
SaajSoapMessage soapMessage = (SaajSoapMessage) message;
SoapHeaderElement messageId = soapMessage.getSoapHeader().addHeaderElement(SessionID_QNAME);
if (StringUtil.validString(sessionId)) {
messageId.setText(sessionId);
}
}
}
Where I use the callback:
JAXBElement<ReturnType> result = (JAXBElement<ReturnType>) getWebServiceTemplate().marshalSendAndReceive(of.createRelease(null), new MySoapActionCallback());

Not able to pass a primitive value to my custom SOAPHandler class

I have written custom soaphandler class which extends javax.xml.rpc.GenericHandler. And my requirement is to pass a primitive variable into this handler class from my other caller java class. This variable should pass at run time and should be thread safe because multiple thread is going to access that handler same time.
I tried to store the value into HandlerConfig object and injected it to the HandlerInfo, but I couldn't found this value in my handler.
This Soap Handler concept is new for me, so please help me to fix this issue.
below I'm posting my raw code for handler class and the class from where I'm calling it.
public class MilerHandler extends GenericHandler {
private HandlerInfo info;
private static final String AUTHORIZATION = "Authorization";
private static final String DATE = "Date";
private static final String URI = "http://-----.com";
public MilerHandler() {
}
public void init(HandlerInfo info) {
this.info = info;
}
public QName[] getHeaders() {
return info.getHeaders();
}
public boolean handleRequest(MessageContext context) {
SOAPMessageContext smc = (SOAPMessageContext)context;
SOAPMessage message = smc.getMessage();
try {
SOAPEnvelope envelope = smc.getMessage().getSOAPPart().getEnvelope();
SOAPFactory factory = SOAPFactory.newInstance();
SOAPElement authorization = factory.createElement(AUTHORIZATION, PCMilerClientService.PREFIX, URI);
SOAPElement date = factory.createElement(DATE, PCMilerClientService.PREFIX, URI);
authorization.addTextNode((String)value1); //Value1 need to be pass from my business class.
date.addTextNode((int)value2); //Value2 need to be pass from my business class.
SOAPElement authHeader = factory.createElement(PCMilerClientService.AUTH_HEADER, PCMilerClientService.PREFIX, URI);
authHeader.addChildElement(authorization);
authHeader.addChildElement(date);
SOAPHeader header = envelope.getHeader();
header.addChildElement(authHeader);
message.saveChanges();
if(log.debug()) {
log.debug(message);
}
}
catch (Exception ex) {
log.error(ex);
}
return true;
}
public boolean handleResponse(javax.xml.rpc.handler.MessageContext context) {
SOAPMessageContext smc = (SOAPMessageContext)context;
SOAPMessage message = smc.getMessage();
if(log.debug()) {
log.debug(message);
}
return true;
}
public boolean handleFault(javax.xml.rpc.handler.MessageContext context) {
SOAPMessageContext smc = (SOAPMessageContext)context;
SOAPMessage message = smc.getMessage();
if(log.debug()) {
log.debug(message);
}
return true;
}
}
public class MilerDistanceLookupWorker {
public void run() {
IService_Stub stub = null;
Service_Impl impl = null;
try {
impl = new Service_Impl();
setPCMilerHandler(impl);
stub = (IService_Stub) impl.getBasicHttpBinding_IService();
} catch (ServiceException e) {
-----------------
}
}
private void setMilerHandler(Service_Impl impl) {
HandlerInfo handlerInfo = new HandlerInfo();
handlerInfo.setHandlerClass(MilerHandler.class);
QName authHeader = new QName(NAMESPACE, AUTH_HEADER, PREFIX);
List<HandlerInfo> headerList = impl.getHandlerRegistry().getHandlerChain(authHeader);
headerList.add(handlerInfo);
impl.getHandlerRegistry().setHandlerChain(authHeader, headerList);
}
}
If you are trying to implement SOAP auth handler then you need to do something like this:
public class SOAPAuthenticationHandler implements SOAPHandler<SOAPMessageContext> {
private String username;
private String password;
public SOAPAuthenticationHandler (String username, String password) {
this.username = username;
this.password = password;
}
#Override
public boolean handleMessage(SOAPMessageContext smc) {
Boolean outboundProperty = (Boolean) smc.get(MessageContext.MESSAGE_OUTBOUND_PROPERTY);
if (!outboundProperty) {
return outboundProperty;
}
try {
SOAPEnvelope envelope = smc.getMessage().getSOAPPart().getEnvelope();
SOAPHeader header = envelope.addHeader();
SOAPElement security = header.addChildElement("Security", "wsse", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd");
security.addNamespaceDeclaration("common", "some xmlns");
SOAPElement usernameToken = security.addChildElement("UsernameToken", "wsse");
usernameToken.addAttribute(new QName("xmlns:wsu"), "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd");
SOAPElement usernameElement = usernameToken.addChildElement("Username", "wsse");
usernameElement.addTextNode(username);
SOAPElement passwordElement = usernameToken.addChildElement("PasswordSaltedDigest", "common");
passwordElement.setAttribute("Type", "http://www.w3.org/2001/04/xmldsig-more#gostr341194");
passwordElement.addTextNode(password);
} catch (SOAPException | DOMException e) {
getLogger().error(e.getMessage());
}
return outboundProperty;
}
#Override
public boolean handleFault(SOAPMessageContext context) {
return false;
}
#Override
public void close(MessageContext context) {
}
#Override
public Set<QName> getHeaders() {
return null;
}
}
And after that you need to add this handler to port' HandlerChain:
List handlerChain = ((BindingProvider) port).getBinding().getHandlerChain();
if (handlerChain == null) {
handlerChain = new ArrayList();
}
handlerChain.add(new SOAPAuthenticationHandler(username, password));
((BindingProvider) port).getBinding().setHandlerChain(handlerChain);

Categories

Resources