Search code examples
grailsgroovycxfcredentialswss4j

How to provide dynamic credentials (username and password) to web service using Grails-cxf plugin


I am using this awesome plugin, http://grails.org/plugin/cxf-client, to consume a contract-first web service with security.

So I already have something like this in my config:

 cxf {
   client {
    cybersourceClient {           
        clientInterface = com.webhost.soapProcessor
        serviceEndpointAddress = "https://webhost/soapProcessor"
        wsdl = "https://webhost/consumeMe.wsdl"
        secured = true
        username = "myUname"
        password = "myPwd"
    }   
}

This works really well, but what I'd like to do now is to provide my users the ability to enter a username and password so they can enter their username and password to consume the service. Does anyone know how to do this?

I suspect that it's using a Custom In Interceptor as in the demo project:

package com.cxf.demo.security

import com.grails.cxf.client.CxfClientInterceptor

import org.apache.cxf.ws.security.wss4j.WSS4JOutInterceptor
import org.apache.ws.security.WSPasswordCallback
import org.apache.ws.security.handler.WSHandlerConstants

import javax.security.auth.callback.Callback
import javax.security.auth.callback.CallbackHandler
import javax.security.auth.callback.UnsupportedCallbackException


class CustomSecurityInterceptor implements CxfClientInterceptor {

String pass
String user


   WSS4JOutInterceptor create() {
    Map<String, Object> outProps = [:]
    outProps.put(WSHandlerConstants.ACTION, org.apache.ws.security.handler.WSHandlerConstants.USERNAME_TOKEN)
    outProps.put(WSHandlerConstants.USER, user)
    outProps.put(WSHandlerConstants.PASSWORD_TYPE, org.apache.ws.security.WSConstants.PW_TEXT)
    outProps.put(WSHandlerConstants.PW_CALLBACK_REF, new CallbackHandler() {

        void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
            WSPasswordCallback pc = (WSPasswordCallback) callbacks[0]
            pc.password = pass
            pc.identifier = user
        }
    })

    new WSS4JOutInterceptor(outProps)
}
}

But as I don't instantiate this interceptor, or understand how it's instantiated, I do not know how I can get the user's credentials used in the interceptor.

Does anyone know how to do this / have any sample code?

Thanks!


Solution

  • Assuming you're using the Spring Security plugin, and the WS credentials you want to use are properties of your User domain object, then something like this should work (untested):

    src/groovy/com/cxf/demo/security/CustomSecurityInterceptor.groovy

    package com.cxf.demo.security
    
    import com.grails.cxf.client.CxfClientInterceptor
    
    import org.apache.cxf.ws.security.wss4j.WSS4JOutInterceptor
    import org.apache.ws.security.WSPasswordCallback
    import org.apache.ws.security.handler.WSHandlerConstants
    
    import javax.security.auth.callback.Callback
    import javax.security.auth.callback.CallbackHandler
    import javax.security.auth.callback.UnsupportedCallbackException
    
    
    class CustomSecurityInterceptor implements CxfClientInterceptor {
    
       def springSecurityService
       def grailsApplication
    
       WSS4JOutInterceptor create() {
        Map<String, Object> outProps = [:]
        outProps.put(WSHandlerConstants.ACTION, org.apache.ws.security.handler.WSHandlerConstants.USERNAME_TOKEN)
        // take default username from config
        outProps.put(WSHandlerConstants.USER, grailsApplication.config.cxf.client.cybersourceClient.username)
        outProps.put(WSHandlerConstants.PASSWORD_TYPE, org.apache.ws.security.WSConstants.PW_TEXT)
        outProps.put(WSHandlerConstants.PW_CALLBACK_REF, new CallbackHandler() {
    
            void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
                WSPasswordCallback pc = (WSPasswordCallback) callbacks[0]
                // take password from current user, fall back to config if no
                // user currently logged in/not in a request thread, etc.
                pc.password = (springSecurityService.currentUser?.wsPassword
                   ?: grailsApplication.config.cxf.client.cybersourceClient.password)
            }
        })
    
        new CustomWSS4JOutInterceptor(springSecurityService, outProps)
      }
    }
    
    class CustomWSS4JOutInterceptor extends WSS4JOutInterceptor {
      def springSecurityService
    
      CustomWSS4JOutInterceptor(springSecurityService, outProps) {
        super(outProps)
        this.springSecurityService = springSecurityService
      }
    
      // overridden to fetch username dynamically from logged in user
      // but fall back on config if no user/not on a request hander thread
      public Object getOption(String key) {
        if(key == WSHandlerConstants.USER && springSecurityService.currentUser?.wsUser) {
          return springSecurityService.currentUser?.wsUser
        } else return super.getOption(key)
      }
    }
    

    grails-app/conf/spring/resources.groovy

    import com.cxf.demo.security.CustomSecurityInterceptor
    beans = {
      customSecurityInterceptor(CustomSecurityInterceptor) {
        springSecurityService = ref('springSecurityService')
        grailsApplication = ref('grailsApplication')
      }
    }
    

    and in the configuration, replace secured = true with securityInterceptor = 'customSecurityInterceptor'

    The same pattern will work if you're not using Spring Security. The crucial bits are the callback handler

                pc.password = (springSecurityService.currentUser?.wsPassword
                   ?: grailsApplication.config.cxf.client.cybersourceClient.password)
    

    and the username logic in getOption

        if(key == WSHandlerConstants.USER && springSecurityService.currentUser?.wsUser) {
          return springSecurityService.currentUser?.wsUser
    

    For example, if the username and password are stored in the HTTP session then instead of the springSecurityService you could use the Spring RequestContextHolder, whose static getRequestAttributes() method returns the GrailsWebRequest being handled by the current thread, or null if the current thread is not processing a request (e.g. if it's a background job).

    RequestContextHolder.requestAttributes?.session?.wsUser
    

    Or if they're request attributes (i.e. you've said request.wsUser = 'realUsername' in the controller) you could use RequestContextHolder.requestAttributes?.currentRequest?.wsUser.