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