I have a tomcat web service that accepts opentravel.org OTA XML requests and responds accordingly. It uses the JibX OTA classes.
So far the users of the service have used POX, and it works really well, but a new user wants to use SOAP and add security credentials to the SOAP Header like this (instead of putting them the POS xml fragment)...
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Header>
<wsse:Security soap:mustUnderstand="1" xmlns:wsse="http://docs.oasis-open.org /wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
<wsse:UsernameToken>
<wsse:Username>USERNAME</wsse:Username>
<wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401- wss-username-token-profile-1.0#PasswordText">SECRET</wsse:Password>
</wsse:UsernameToken>
</wsse:Security>
</soap:Header>
So to authenticate the request I think I need to access the headers from within the service implementation class.
I checked out the SOAP Headers example, which I think tells me that I can access the headers by also including a inContext, e.g.
public RoomListRS list(RoomListRQ roomListRQ, InContext inCtx){
....
}
so within this method I can do this...
Security security = (Security ) inCtx.getAttribute("security");
so I can access the username token within,
...having specified this in the service...
<service name="OTAService">
<service-class>com.xx.webservice.ota.HotelServiceImpl</service-class>
<operation method="list"/>
<handler-class class="org.jibx.ws.io.handler.ContextAttributeUnmarshallingInHandler">
<constructor-arg value="com.xx.shared.soap.security.Security"/>
<constructor-arg value="security"/>
</handler-class>
</service>
Have I got that right?
So I created the Security class, but left out all the namespace stuff to start with just to get going and prove that I can access something in the header. Based on having a fragment like this...
<Security>
<UsernameToken>
<Username>USERNAME</Username>
<Password>SECRET</Password>
</UsernameToken>
</Security>
So I created the binding with bindgen, then compiled, then called it with soapUI
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns="http://www.opentravel.org/OTA/2003/05">
<soapenv:Header>
<Security>
<UsernameToken>
<Username>USERNAME</Username>
<Password>SECRET</Password>
</UsernameToken>
</Security>
</soapenv:Header>
<soapenv:Body>
<OTA_HotelRoomListRQ xmlns="http://www.opentravel.org/OTA/2003/05" Version="2.0">
....
</OTA_HotelRoomListRQ>
</soapenv:Body>
</soapenv:Envelope>
but when I try to get the Security object from the context it is null.
Have I got the wrong end of the stick?
Should I just create another service with a different endpoint using something more SOAPY?
Is what I am trying to do not possible with JibX WS and the inHandler?
Any comments most welcome.
thank you so much for taking the trouble to answer my question.
I am trying to go through what you have added. I used your customisation and the xsd to create the java source and binding.xml.
I have compiled the classes and I am now trying to bind them, but I am getting this error:
C:\Java\wsse>java org.jibx.binding.generator.BindGen org.oasisopen.docs.wss.oasis200401wsswssecuritysecext1.SecurityHeaderType
Exception in thread "main" java.lang.IllegalStateException: No way to handle type java.lang.Object, referenced from org.oasisopen.docs.wss.oasis200401wsswssecuritysecext1.SecurityHeaderType
at org.jibx.binding.generator.BindGen.expandReferences(BindGen.java:227)
at org.jibx.binding.generator.BindGen.findReferences(BindGen.java:1010)
at org.jibx.binding.generator.BindGen.generate(BindGen.java:1124)
at org.jibx.binding.generator.BindGen.main(BindGen.java:1302)
I am going to take a look at bindgen customisations to see if that sheds any light, as that's the only clue given in response to this issue. Could you tell me how you got around this?
Thanks again.
I can share some production code that we have for WS-Security username/password headers. I can't see why your code isn't working, but maybe this will help.
We generated the WS-Security code and bindings from the oasis-200401-wss-wssecurity-secext-1.0.xsd schema, with the following customisation:
<!-- Contains customization elements for code generation from WS Security schema -->
<schema-set show-schema="false" generate-all="false" xmlns:xs="http://www.w3.org/2001/XMLSchema" line-width="120">
<schema name="oasis-200401-wss-wssecurity-secext-1.0.xsd" generate-all="true" prefer-inline="true" any-handling="mapped">
<class-decorator class="org.jibx.schema.codegen.extend.CollectionMethodsDecorator" />
</schema>
</schema-set>
which is then built with:
<target name="codegen-wss" description="Regenerate JiBX bindings and generated code for WS-Security schema">
<echo message="Running code generation from schema" />
<mkdir dir="${gen.src.dir}" />
<java classname="org.jibx.schema.codegen.CodeGen" fork="yes" classpathref="build.classpath" failonerror="true">
<arg value="-c" />
<arg value="custom_jibx_gen_wssec.xml" />
<arg value="-t" />
<arg value="${gen.src.dir}" />
<arg value="wsdl/wssec/oasis-200401-wss-wssecurity-secext-1.0.xsd" />
</java>
<move file="${gen.src.dir}/binding.xml" tofile="${wssec.binding.file}" failonerror="true" />
</target>
and bound with:
<target name="compile" depends="init" description="Compile the source code and run JiBX binding compiler">
<mkdir dir="${dest.dir}" />
<javac srcdir="${src.dir}:${gen.src.dir}" destdir="${dest.dir}" deprecation="on">
<classpath refid="build.classpath" />
</javac>
<bind binding="${gen.src.dir}/xxx-binding.xml">
<classpath path="${dest.dir}" />
</bind>
<bind binding="${wssec.binding.file}">
<classpath path="${dest.dir}" />
</bind>
</target>
The Spring config for the servlet defines the InHandler:
<property name="handlerDefinitions">
<list>
<bean class="org.jibx.ws.server.HandlerDefinition" >
<description>Handler for inbound WS/Security header</description>
<property name="className" value="org.jibx.ws.io.handler.ContextAttributeUnmarshallingInHandler" />
<property name="args">
<list>
<value>org.oasisopen.docs.wss.oasis200401wsswssecuritysecext1.SecurityHeaderType</value>
<value>wssecurity.header</value>
</list>
</property>
</bean>
and the endpoint retrieves the header using:
public ServiceRequestReceipt processRequest(ServiceRequest request, InContext inCtx, OutContext outCtx)
throws WsException {
SecurityHeaderType securityHeader = (SecurityHeaderType) inCtx.getAttribute("wssecurity.header");
Our code to use the securityHeader looks like:
if (securityHeader == null) {
throw new AuthenticationException("No WS-Security header found");
}
List<Object> securityHeaderTypes = securityHeader.getSecurityHeaderTypes();
if (securityHeaderTypes == null || securityHeaderTypes.size() == 0) {
throw new AuthenticationException("WS-Security header appears to be empty");
}
UsernameTokenType usernameToken = null;
try {
usernameToken = (UsernameTokenType) securityHeaderTypes.get(0);
} catch (ClassCastException e) {
throw new AuthenticationException("Expected UsernameToken in WS-Security header");
}
AttributedString usernameAttStr = usernameToken.getUsername();
if (usernameAttStr == null) {
throw new AuthenticationException("Expected Username in WS-Security header");
}
String username = usernameAttStr.getString();
if (!username.equals(retailer.getRetailerUsername())) {
throw new AuthenticationException("Invalid username in WS-SecurityHeader");
}
List<Object> any = usernameToken.getAny();
if (any == null) {
throw new AuthenticationException("Expected Password element in WS-Security header");
}
PasswordString passwordString = null;
for (Iterator iterator = any.iterator(); iterator.hasNext();) {
try {
passwordString = (PasswordString) iterator.next();
} catch (ClassCastException ignore) {
logger.debug("Found non password string object");
}
}
if (passwordString == null) {
throw new AuthenticationException("Expected Password in WS-Security header");
}
if (passwordString.getAttributedString() == null) {
throw new AuthenticationException("Expected Password AttributedString in WS-Security header");
}
String password = passwordString.getAttributedString().getString();
if (!password.equals(retailer.getRetailerPassword())) {
throw new AuthenticationException("Invalid password in WS-SecurityHeader");
}
I hope that helps!