5

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!

lilalfyalien
  • 203
  • 1
  • 3
  • 9

2 Answers2

0

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.

Ian Roberts
  • 120,891
  • 16
  • 170
  • 183
  • Thanks for your response- but I worked out the Groovier way of doing it :D – lilalfyalien Dec 04 '12 at 15:04
  • Thanks- how about if I just use a factory to session-specific produce CustomSecurityInterceptors? – lilalfyalien Dec 04 '12 at 15:49
  • @lilalfyalien I've added a bit more explanation of the `RequestContextHolder`. – Ian Roberts Dec 04 '12 at 16:28
  • If my username and password is stored in the request object, what role is the `getOption()` method in `CustomWSS4JOutInterceptor` performing? I.e why can I not just set the username at the same time as I set the password? – lilalfyalien Dec 04 '12 at 16:42
  • The `request` object / `RequestContextHolder` does not exist in the context of the interceptor... I am getting 'No such field' exceptions... – lilalfyalien Dec 04 '12 at 16:56
  • @lilalfyalien have you added the appropriate `import`? – Ian Roberts Dec 04 '12 at 17:07
  • @lilalfyalien Glad it helped. When you get a good answer that solves your problem, it's good manners to "accept" it by clicking on the tick mark to the left. If you have a track record of accepting good answers, people are more likely to help you again in future. – Ian Roberts Dec 04 '12 at 17:22
0

Here's a generic answer for anyone else:

1. Config.groovy

cxf {
    client {

        nameOfClient {
            clientInterface = com.webhost.soapProcessor
            serviceEndpointAddress = "https://webhost/soapProcessor"
            wsdl = "https://webhost/soapProcessorconsumeMe.wsdl"
            secured = true
            securityInterceptor = "nameOfSecurityInterceptorBean"
        }
    }
}

2. Resources.groovy

import com.company.package.MySecurityInterceptor

beans = {

nameOfSecurityInterceptorBean(MySecurityInterceptor) {
}

}

3. Create a MySecurityInterceptor under com.company.package

package com.company.package;

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

import org.springframework.web.context.request.RequestContextHolder

class MySecurityInterceptor implements CxfClientInterceptor {


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]
                    def requestObj = RequestContextHolder.requestAttributes?.currentRequest
                    pc.password = requestObj.soapPassword
                    pc.identifier = requestObj.soapIdentifier
                }
            })

    new WSS4JOutInterceptor(outProps)
}
}

4. Now we need to put the username and password in the request (thread-safe) to be pulled out by the interceptor:

    import com.company.package.MySecurityInterceptor
    class MySoapSendingController {

    SoapProcessor nameOfClient


    def index() {

    request['soapIdentifier'] = "usernameToUse"
    request['soapPassword'] = "passwordToUse"


                 ...

        ReplyMessage replyMsg = nameOfClient.makeSOAPRequest(request)
       }
    }
lilalfyalien
  • 203
  • 1
  • 3
  • 9
  • 1
    This is not thread safe - if two different users both hit this controller action at the same time you may end up with one of them using the other's credentials, or worse, them both trying to use the password of one user with the username of the other... – Ian Roberts Dec 04 '12 at 15:07
  • Damn! I'm not using SpringSecurity so are there any other options? – lilalfyalien Dec 04 '12 at 15:09
  • Is there a way I could maintain some kind of semi-permanent array of users in my interceptor with usernames and passwords for each one? – lilalfyalien Dec 04 '12 at 15:10