I override the WSS4JInInterceptor to get the WS-Security details from the message, these are put into a Spring UsernamePasswordAuthenticationToken then placed into the messages's exchange under the key "WS_TOKEN". The CXF Architecture defines an exchange as holding "references to the in, out and fault messages for the current message exchange"; I wanted to include the value in the actual Message object but only the exchange was available to the method invocation (described later) - exchange would have to do. The following is the code for SpringAuthnInterceptor.groovy:
package ws import org.apache.cxf.ws.security.wss4j.WSS4JInInterceptor import org.apache.cxf.binding.soap.SoapMessage import org.apache.cxf.interceptor.Fault import org.apache.ws.security.handler.WSHandlerConstants import org.apache.ws.security.handler.WSHandlerResult import org.apache.ws.security.WSSecurityEngineResult import org.apache.ws.security.WSConstants import org.apache.ws.security.WSUsernameTokenPrincipal import org.springframework.security.authentication.UsernamePasswordAuthenticationToken import org.apache.cxf.ws.security.SecurityConstants public class SpringAuthnInterceptor extends WSS4JInInterceptor { public SpringAuthnInterceptor() { super() } public SpringAuthnInterceptor(Mapproperties) { super (properties) } //with help from: http://forum.springsource.org/showthread.php?64492-WS-Security-integration public void handleMessage(SoapMessage message) throws Fault { message.put(SecurityConstants.VALIDATE_TOKEN, Boolean.FALSE); super.handleMessage(message) message.getContextualProperty(WSHandlerConstants.RECV_RESULTS).each { WSHandlerResult result -> result.getResults().each { WSSecurityEngineResult securityResult -> if ((securityResult.get(WSSecurityEngineResult.TAG_ACTION) & WSConstants.UT) > 0) { WSUsernameTokenPrincipal principal = (WSUsernameTokenPrincipal)securityResult.get(WSSecurityEngineResult.TAG_PRINCIPAL) if (!principal.getPassword()) principal.setPassword("") UsernamePasswordAuthenticationToken usernameToken = new UsernamePasswordAuthenticationToken(principal.getName(), principal.getPassword()) message.getExchange().put("WS_TOKEN", usernameToken) } } } } }
Once the user details are populated we need to override BeanInvoker to change the way that the WS method is invoked to add, then remove the SecurityContext. The following is SpringSecurityWSInvoker.groovy which carries this out:
package ws import org.apache.cxf.service.invoker.BeanInvoker import org.apache.cxf.message.Exchange; import java.lang.reflect.Method import org.springframework.security.authentication.UsernamePasswordAuthenticationToken import org.springframework.security.core.context.SecurityContextHolder class SpringSecurityWSInvoker extends BeanInvoker { public SpringSecurityWSInvoker(Object proxy) { super(proxy) } protected Object performInvocation(Exchange exchange, final Object serviceObject, Method m, Object[] paramArray) throws Exception { Object invocationResult = null try { UsernamePasswordAuthenticationToken token = (UsernamePasswordAuthenticationToken)exchange.get("WS_TOKEN") if (token) { SecurityContextHolder.getContext().setAuthentication(token) } invocationResult = super.performInvocation(exchange,serviceObject,m,paramArray) } finally { SecurityContextHolder.clearContext() } return invocationResult } }
Now we can use Spring to set up the relevant web service with the additional interceptor and overridden method invoker. I've used the following with the Spring DSL (I'm sure it translates to Spring XML easily also):
wsServerFactory(org.apache.cxf.frontend.ServerFactoryBean) { serviceClass = wsInterface address = "${ws.protocol}://${ws.host}:${ws.port}/${ws.serviceName}" invoker = new SpringSecurityWSInvoker(wsInterfaceInstance) inInterceptors = [new SpringAuthnInterceptor(["action":"UsernameToken","passwordType":"PasswordText"])] serviceName = new QName(ws.namespace,ws.serviceName,"") }Where wsInterface is the webservice interface code that forms the WSDL and wsInterfaceInstance is the instantce of the implementation of this interface. To start the server the following code could be used:
def serverFactory = appContext.getBean("wsServerFactory") serverFactory.create()
No comments:
Post a Comment