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!
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
.