To recap the previous article, it is very simple to expose a code first webservice using Apache CXF with Spring. Apache CXF provides a custom Spring namespace to easily configure the endpoint:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:jaxws="http://cxf.apache.org/jaxws"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://cxf.apache.org/jaxws http://cxf.apache.org/schemas/jaxws.xsd">
<import resource="classpath:META-INF/cxf/cxf.xml"/>
<import resource="classpath:META-INF/cxf/cxf-extension-soap.xml"/>
<bean id="memberendpoint" class="org.bk.memberservice.endpoint.DefaultMemberEndpoint"/>
<jaxws:endpoint address="/memberservice" id="memberservicehttp" implementor="#memberendpoint" >
</jaxws:endpoint>
</beans>
This exposes the memberendpoint bean above as a fully configured webservice.To secure this service using usernametoken, first implement a callback for CXF to invoke to validate the credentials passed by the user:
package org.bk.memberservice.endpoint;
import java.io.IOException;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.UnsupportedCallbackException;
import org.apache.ws.security.WSPasswordCallback;
public class UsernameTokenCallback implements CallbackHandler {
@Override
public void handle(Callback[] callbacks) throws IOException,
UnsupportedCallbackException {
Callback callback = callbacks[0];
WSPasswordCallback pc = (WSPasswordCallback) callback;// Retrieve and set the real password..which will be validated
// by CXF validator against the digest password
pc.setPassword("test");
System.out.println("Received cred: " + pc.getIdentifier() + " : " + pc.getPassword());
// validate the credentials
// boolean isValid = true;
// throw IO Exception if the credentials are not valid
// if (!isValid) {
// throw new IOException("Bad Credentials");
// }
}
}
To wire this Callback handler with the service endpoint, CXF uses a concept called the interceptor, basically the webservice call is handled by the interceptors before being handed over to the service.
<jaxws:endpoint address="/memberservice" id="memberservicehttp"
implementor="#memberendpoint">
<jaxws:inInterceptors>
<bean class="org.apache.cxf.ws.security.wss4j.WSS4JInInterceptor">
<constructor-arg>
<map>
<entry key="action" value="UsernameToken" />
<entry key="passwordType" value="PasswordDigest" />
<entry key="passwordCallbackRef">
<ref bean="usernameTokenCallback" />
</entry>
</map>
</constructor-arg>
</bean>
</jaxws:inInterceptors>
</jaxws:endpoint>
That's it!! the endpoint is now secured using UsernameToken profile. To test this, bring up the endpoint, use SOAP UI to send a normal request, it will fail with the message that a ws-security header is required - this is the username token soap header that is expected as part of the request.Updated Sample available at: git://github.com/bijukunjummen/memberservice-codefirst.git
Reference:
1. WS-Security reference in Wikipedia: http://en.wikipedia.org/wiki/WS-Security
2. WS-Secuirty usernametoken profile specs at OASIS: http://www.oasis-open.org/committees/download.php/16782/wss-v1.1-spec-os-UsernameTokenProfile.pdf
3. Apache CXF reference: http://cxf.apache.org/docs/ws-security.html
4. New changes as part of WSS4J - http://coheigea.blogspot.com/2011/02/wspasswordcallback-changes-in-wss4j-16.html
However, the WSDL doesn't contain the security policy. This make it difficult for client to know that WS-Security is to be used.
ReplyDeletenicely & cleanly explained
ReplyDeleteRaman