So I have a web service with several namespaces that I would like to route through a bean to do some checking of user credentials. Its been a long time since I used XPATH so I might just be having a PICNIC(Problem In Chair Not In Computer Moment) error.
The web service message will always have the following structure/pattern :
<Operation>
<header with the head name space where the user credentials are stored>
<record control>
<objXX>
</Operation>
Here is a example message(SOAP UI):
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:list="http://www.insol.irix.com.au/IRIX_V1/Debtors/List" xmlns:head="http://www.insol.irix.com.au/IRIX_V1/Headers" xmlns:rec="http://www.insol.irix.com.au/IRIX_V1/RecordControl">
<soapenv:Header/>
<soapenv:Body>
<list:ListDebtorReq>
<head:MsgReqHdr>
<head:MsgGUID>${=java.util.UUID.randomUUID()}</head:MsgGUID>
<head:MsgDateTime>${=javax.xml.datatype.DatatypeFactory.newInstance().newXMLGregorianCalendar(GregorianCalendar.getInstance())}</head:MsgDateTime>
<head:ConsumerSystemIDInfo>
<head:ConsumerSystemID>ConsumerSystemID</head:ConsumerSystemID>
<head:ConsumerSystemUserID>AgentX</head:ConsumerSystemUserID>
</head:ConsumerSystemIDInfo>
<head:SecCredInfo>
<head:IRIXUserID>Some User ID</head:IRIXUserID>
<head:IRIXPassword>Some Password</head:IRIXPassword>
</head:SecCredInfo>
<head:CryptoInfo>
<head:DigitalSignatureInfo>
<head:DigitalSignatureValue>verrantque per auras</head:DigitalSignatureValue>
<head:DigitalSignatureAlgorithm>SHA-256</head:DigitalSignatureAlgorithm>
</head:DigitalSignatureInfo>
</head:CryptoInfo>
</head:MsgReqHdr>
<!--Optional:-->
<rec:RecCntrl>
<rec:StartRecordNumber>1</rec:StartRecordNumber>
<!--Optional:-->
<rec:NumberOfRecords>3</rec:NumberOfRecords>
</rec:RecCntrl>
</list:ListDebtorReq>
</soapenv:Body>
</soapenv:Envelope>
So essentially I want to be able to create a bean that will be able to query the MsgReq header for all the user name and password data. To simplify things I am just trying to query the MsgGUID and work my way from there. However I cant seem to get the xpath right. Since I am using several namespaces I have included them in the camel context file just to make sure they are available.
Here is my camel-context:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:camel="http://camel.apache.org/schema/spring"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://camel.apache.org/schema/spring
http://camel.apache.org/schema/spring/camel-spring.xsd">
<import resource="classpath:META-INF/spring/camel-cxf.xml" />
<bean id="SecurityCheckBean" class="au.com.irix.insol.Security.IRIXSecurity"/>
<camelContext xmlns="http://camel.apache.org/schema/spring"
xmlns:list="http://www.insol.irix.com.au/IRIX_V1/Debtors/List"
xmlns:head="http://www.insol.irix.com.au/IRIX_V1/Headers"
xmlns:rec="http://www.insol.irix.com.au/IRIX_V1/RecordControl">
<route>
<from uri="cxf:bean:DebtorsService?dataFormat=PAYLOAD"/>
<bean ref="SecurityCheckBean"/>
</route>
</camelContext>
</beans>
As you can see I am running the incoming message of the web service producer to the SecurityCheckBean. My SecurityCheckBean is super simple at the moment see code below.
public class IRIXSecurity {
public void CheckCredentials(
#XPath("//head:MsgGUID") String msgGUID,
#Body String body){
System.out.println(body);
System.out.println("Check Credentials Invoked");
System.out.println(msgGUID);
}
}
However when I send a send a request via soap UI I get the following exception:
Invalid xpath: //head:MsgGUID. Reason: javax.xml.xpath.XPathExpressionException: net.sf.saxon.trans.XPathException: Prefix head has not been declared
So how do I go about retrieving this information? Why even though I have declared the name spaces in my camel-context.xml they are reported as missing?
Just for interest sake I have tried several variations of the XPATH such as:
#XPath("//MsgGUID")
#XPath("//MsgReqHdr/head:MsgGUID")
#XPath("//head:MsgReqHdr/head:MsgGUID")
Every time I either get an exception as listed above or a NULL value...
Right got it to work. When dealing with the namespaces in a bean the following syntax must be used to include the namespaces.
public class IRIXSecurity {
public void CheckCredentials(
//#Body ListDebtorReqType msgBody, #Headers Map hdr,
#XPath(value="//header:MsgGUID",namespaces = #NamespacePrefix(
prefix = "header",
uri = "http://www.insol.irix.com.au/IRIX_V1/Headers")) String msgGUID,
#Body Document xml)
{
System.out.println("Check Credentials Invoked");
System.out.println(msgGUID);
//exchange.getOut().setBody(debtorRsType);
}
}
Related
I have a Soap service generated by a wsdl file, that expects a certain TargetNamespace
#WebResult(name = "getResponse", targetNamespace = "http://targetNameSpace1.com", partName = "result")
but we have multiple clients calling this api and each one uses a diferente TargetNamespace:
Client one:
<soap:Envelope
xmlns:loc="http://targetNameSpace1.com">
<soap:Header>
<ns3:RequestSOAPHeader>
...
</ns3:RequestSOAPHeader>
</soap:Header>
<soap:Body>
<loc:getResponse>
<loc:value>url/</loc:value>
</loc:getResponse>
</soap:Body>
</soap:Envelope>
Client two:
<soap:Envelope
xmlns:loc="http://targetNameSpace2.com">
<soap:Header>
<ns3:RequestSOAPHeader>
...
</ns3:RequestSOAPHeader>
</soap:Header>
<soap:Body>
<loc:getResponse>
<loc:value>url/</loc:value>
</loc:getResponse>
</soap:Body>
</soap:Envelope>
This is the error i get:
<soap:Envelope>
<soap:Body>
<soap:Fault>
<faultcode>soap:Client</faultcode>
<faultstring>Message part {http://targetNameSpace2.com}getResponse was not recognized. (Does it exist in service WSDL?)</faultstring>
</soap:Fault>
</soap:Body>
</soap:Envelope>
and i can only make it work with one at a time changing the targetNameSpace tag in the webResult, but my ultimate goal is to ignore this tag, because i dont know witch namespace will each client use.
At the moment i am trying to use an interceptor that extends this
AbstractSoapInterceptor and i get a SoapMessage object i can acess it before the request is made, but i can't seem to change the request, not sure if its the best aproach.
Does anyone have a solution for this?
Thanks!
The wsdl and the (embedded) xsd are the contracts that you specify. Server and client need to follow that contract. Changing the contract single sided will result into invalid messages. Instead of looking for a solution to accept invalid messages you should update the clients so they obey the contracts.
I ended up following this article https://www.javatips.net/blog/cxf-interceptor-example,
I intercept every request, and replace this:
xmlns:loc="http://targetNameSpace2.com"
with the url that i want, i used regex to replace whats inside the loc tag
I am using Camel in our project and requesting WebServices, the dataFormat is POJO. I was able to request when my SOAP message did not contain SOAP headers, but when it had Headers, I was unable to set those. I looked at the documentation but was not able to understand and have several questions.
I want to create a message like the below:
<soapenv:Envelope`enter code here`
xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<soapenv:Header>
<platformMsgs:documentInfo
xmlns:platformMsgs="urn:messages_2015_1.platform.webservices.netsuite.com">
<platformMsgs:nsId>WEBSERVICES_3479023</platformMsgs:nsId>
</platformMsgs:documentInfo>
</soapenv:Header>
<soapenv:Body>
<addListResponse
xmlns="">
<platformMsgs:writeResponseList
xmlns:platformMsgs="urn:messages_2015_1.platform.webservices.netsuite.com">
<platformCore:status isSuccess="true"
xmlns:platformCore="urn:core_2015_1.platform.webservices.netsuite.com"/>
<platformMsgs:writeResponse>
<platformCore:status isSuccess="false"
xmlns:platformCore="urn:core_2015_1.platform.webservices.netsuite.com">
<platformCore:statusDetail type="ERROR">
<platformCore:code>DUP_ENTITY</platformCore:code>
<platformCore:message>This entity already exists.</platformCore:message>
</platformCore:statusDetail>
</platformCore:status>
</platformMsgs:writeResponse>
</platformMsgs:writeResponseList>
</addListResponse>`enter code here`
</soapenv:Body>
</soapenv:Envelope>
I will be able to send the message if there was only Body, but can someone give me a code snippet for including the header section? The dataFormat is POJO.
When using CXF endpoint with dataFormat as POJO, body in Camel Exchange object is an object of org.apache.cxf.message.MessageContentsList. It is an extension of java.util.ArrayList<Object> and it contains parts of SOAP Message in order as defined in WSDL and corresponding method in WebService class.
Element 0 there is a Body.
So, one way to do that with Java is to create a Processor class implementing org.apache.camel.Processor interface and in its process method set your SOAP header. Something like:
#Override
public void process(Exchange camelExchange) throws Exception {
MessageContentsList messageBody = (MessageContentsList) camelExchange.getIn().getBody();
DocumentInfo docInfoHeader = new DocumentInfo();
... set docInfoHeader properties ...
messageBody.add(docInfoHeader);
}
(sample is not tested. It is just an idea, how to handle that...)
Other answer on similar question you can find here: Setting Custom Soap Header-To Pojo Message In Camel Cxf
It describes how to use Camel Exchange headers as SOAP Headers.
I'm not sure for 100% which way will work for you and which one is better...
I guess, it depends on WSDL you use.
UPD: second choice is to use pure CXF solution by using CxfMessageSoapHeaderOutInterceptor custom implementation.
It may look like:
public class MyCxfInterceptor extends CxfMessageSoapHeaderOutInterceptor {
#Override
public void handleMessage( org.apache.cxf.binding.soap.SoapMessage message) {
org.apache.cxf.binding.soap.SoapHeader myCustomHeader = new org.apache.cxf.binding.soap.SoapHeader(new QName(
{custom name space}, {custom local name}), {Custom content object}));
myCustomHeader.setMustUnderstand(true);
message.getHeaders().add(myCustomHeader);
}
and set Interceptor in Camel Cxf Endpoint as :
<cxfEndpoint ...>
<outInterceptors>
<spring:bean class="MyCxfInterceptor"/>
</outInterceptors>
...
Well suppose I request the Web Service and it failed, a Fault message is generated. Will I get the Fault object at position 0 of MessageContentsList then too? Or will I get only the response object at position 0?
What is needed:
There is simple web application running on Tomcat at address http://localhost:8080/.
Handler for following URL should be added:
GET http://localhost:8080/request/report/custom_report?from=2013-10-12&to=2014-10-12&download=true
which will simply write to the HttpServletResponse some data i.e. no views are involved.
What was done:
As per official Spring MVC documentation following mapping of DispatcherServlet was added to web.xml
<servlet>
<servlet-name>springDispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>springDispatcher</servlet-name>
<url-pattern>/request/*</url-pattern>
<!-- PLEASE NOTE that mapping to /* is not an option -->
</servlet-mapping>
Now, because latest spring-webmvc-4.0.5.RELEASE is used I would like to add above mentioned handler with the minimum XML or Java configuration possible, so I create controller class:
package org.yura.servlet.spring;
#Controller
public class SpringRequestController {
#RequestMapping(value = "/report/custom_report",
method = GET,
produces = "application/pdf")
public void getCustomReport(
#RequestParam("from") #DateTimeFormat(pattern = "yyyy-MM-dd") final Date from,
#RequestParam("to") #DateTimeFormat(pattern = "yyyy-MM-dd") final Date to,
#RequestParam("download") final boolean download,
final HttpServletResponse response) throws IOException {
takeParamsAndWriteReportAsPdfToServletResponse(from, to, download, response.getOutputStream());
}
Then, in order for this Controller to be "picked up" by Spring I put springDispatcher-servlet.xml right next to web.xml in WEB-INF folder with following configuration (please advise if it can be simplified even more):
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.0.xsd">
<context:component-scan base-package="org.yura.servlet.spring" />
</beans>
The Problem
With this configuration, after starting Tomcat and navigating to above mentioned URL I get Error 404.
Question 1: Please advise what is wrong with handler URLs - should I specify them as relative or what? (because as per logs, DispatcherServlet is created normally)
Question 2: Is it possible to move configuration from springDispatcher-servlet.xml to my Controller class in order not to scatter request-handling logic across multiple files.
Thanks in advance...
You haven't enabled your MVC stack. Add
<mvc:annotation-driven />
to your springDispatcher-servlet.xml (along with the appropriate namespaces).
The configuration in springDispatcher-servlet.xml is not simply request handling configuration. It can contain any bean declaration. If anything, you can move it to a Java configuration, but it should not be part of your #Controller source code.
I have followed a mash up of a couple tutorials and neither too succesfully!
I am trying to get Apache CXF and WS-Security to call back to my Spring Security authenticator. All is close to working but at tne moment I have a problem getting the password to give Spring security out of the WS-call back.
The Handler below gets galled but pc.getPassword() is null. I want this to be the password sent in Soap so I can pass it to spring
public class ServerPasswordCallback implements CallbackHandler {
public void handle(Callback[] callbacks) throws IOException,
UnsupportedCallbackException {
WSPasswordCallback pc = (WSPasswordCallback) callbacks[0];
pc.setPassword( pc.getPassword() );
}
My interceptor is set up as so
<bean id="wsAuthenticationInterceptor" class="com.olympus.viewtheworld.server.security.auth.WSAuthenticationInInterceptor">
<constructor-arg index="0">
<map key-type="java.lang.String" value-type="java.lang.Object">
<entry key="action" value="UsernameToken" />
<entry key="passwordType" value="PasswordText" />
<entry key="passwordCallbackClass" value="com.olympus.viewtheworld.server.security.auth.ServerPasswordCallback" />
</map>
</constructor-arg>
<property name="authenticationManager" ref="authenticationManager"/>
</bean>
<jaxws:endpoint id="secureHelloService"
implementor="#secureHelloServiceImpl"
implementorClass="com.olympus.viewtheworld.server.service.Impl.SecureHelloServiceImpl"
address="/SoapService/secure/hello">
<jaxws:serviceFactory>
<ref bean="jaxws-and-aegis-service-factory" />
</jaxws:serviceFactory>
<jaxws:inInterceptors>
<bean class="org.apache.cxf.binding.soap.saaj.SAAJInInterceptor"/>
<ref bean="wsAuthenticationInterceptor" />
</jaxws:inInterceptors>
</jaxws:endpoint>
And the soap request I am sending out of SoapUI is
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:test="http://test/">
<soapenv:Header>
<wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
<wsse:UsernameToken>
<wsse:Username>rob2</wsse:Username>
<wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText">passwordxx</wsse:Password>
</wsse:UsernameToken>
</wsse:Security>
</soapenv:Header>
<soapenv:Body>
<test:hello>
<!--Optional:-->
<hello>asdf</hello>
</test:hello>
</soapenv:Body>
</soapenv:Envelope>
Version wise it is Spring 3.1 and CXF 2.7.0
What do I need to do to see "passwordxx" in the ServerPasswordCallback class? Is it the Soap request, the config or just wrong?!
Cheers,
Rob
It appears from the documentation on org.apache.ws.security.handler.WSHandlerConstants.PW_CALLBACK_CLASS that the method should instead call pc.setPassword with the stored password to compare the user provided password against as argument, instead of the user provided password itself:
This tag refers to the CallbackHandler implementation class used to
obtain passwords. The value of this tag must be the class name of a
javax.security.auth.callback.CallbackHandler instance. The callback function
javax.security.auth.callback.CallbackHandler.handle(javax.security.auth.callback.Callback[])
gets an array of org.apache.ws.security.WSPasswordCallback objects.
Only the first entry of the array is used. This object contains the
username/keyname as identifier. The callback handler must set the
password or key associated with this identifier before it returns. The
application may set this parameter using the following method:
call.setProperty(WSHandlerConstants.PW_CALLBACK_CLASS, "PWCallbackClass");
I'm using a org.apache.ws.security.validate.Validator to check the validity of the supplied password, and setting the Spring security context there:
#Bean(name = "wssforjInInterceptor")
public WSS4JInInterceptor wssforjInInterceptor() {
// Configure how we ask for username and password
Map<String, Object> props = new HashMap<>();
props.put(WSHandlerConstants.ACTION, WSHandlerConstants.USERNAME_TOKEN);
props.put(WSHandlerConstants.PASSWORD_TYPE, WSConstants.PW_TEXT);
// Password callback
props.put(WSHandlerConstants.PW_CALLBACK_REF, passwordCallbackHandler());
// Validator registration
Map<QName, Object> validators = new HashMap<>();
String WSS_WSSECURITY_SECEXT_1_0_XSD = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd";
QName qName = new QName(WSS_WSSECURITY_SECEXT_1_0_XSD, WSHandlerConstants.USERNAME_TOKEN, "");
validators.put(qName, usernameTokenValidator());
props.put(WSS4JInInterceptor.VALIDATOR_MAP, validators);
WSS4JInInterceptor wss4jInInterceptor = new WSS4JInInterceptor(props);
return wss4jInInterceptor;
}
I'm not sure whether that approach is any better or worse (I'd appreciate some feedback on this), but perhaps it's useful for the next person to come across this issue. There appears to be a lack of decent up to date documentation on how to integrate Spring security and CXF.
Ok so this is not the ideal solution and hopefully a better answer will come along a bit later down the line, most likely when I have some more time to look at this.
I use a regex to inspect the full packet and pull out the password field. Code is below. Will update later when I work out the right way to do this as this is defintley not it!
WSPasswordCallback pc = (WSPasswordCallback) callbacks[0];
String mydata= pc.getRequestData().getMsgContext().toString();
Pattern pattern = Pattern.compile("<wsse:Password Type=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText\">(.*?)<");
Matcher matcher = pattern.matcher(mydata);
if (matcher.find())
{
pc.setPassword( matcher.group(1) );
}
I have a jaxws webservice with #SchemaValidation on it.
#SchemaValidation(handler = MySchemaValidationHandler.class)
#WebService(portName = "MyService", serviceName = "MyService", targetNamespace = "urn:com.my.urn", wsdlLocation = "/wsdls/mywsdl.wsdl", endpointInterface = "com.my.MyService")
#BindingType("http://schemas.xmlsoap.org/wsdl/soap/http")
public class MyServiceImpl
implements MyService {
#Resource
WebServiceContext wsContext;
public void myOperation(Holder<XMLGregorianCalendar> tag1, Holder<XMLGregorianCalendar> tag2, Holder<String> message) {
...
}
}
in xsd I have the following example:
<xsd:choice>
<xsd:element name="tag1" type="xsd:dateTime" minOccurs="0"/>
<xsd:element name="tag2" type="xsd:dateTime" minOccurs="0"/>
</xsd:choice>
When I send the request with empty tags, I get an error concerning the field format (doesn't fit the restrictions of a date format).
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:urn="urn:com.my.urn">
<soapenv:Header/>
<soapenv:Body>
<urn:myOperation>
<Tag1>value1</Tag1>
<Tag2></Tag2>
</urn:myOperation>
</soapenv:Body>
</soapenv:Envelope>
In request description I have mandatory tags and choice tags and have to return the correct error message in the response, so I can't just skip such errors in the handler.
Also I can't modify nor xsd nor wsdl
Logically empty tags mean the same as missing tags. How can I make the validation treat empty tags as missing or how can I delete empty tags before the validation?
Thanks.
Edit: Don't send invalid XML. In the example you give, you should remove <Tag2></Tag2> in whatever your client code is. Don't try to abuse the server's validation to handle invalid XML.