【问题标题】:How to provide dynamic credentials (username and password) to web service using Grails-cxf plugin如何使用 Grails-cxf 插件向 Web 服务提供动态凭据(用户名和密码)
【发布时间】:2012-12-04 11:22:36
【问题描述】:

我正在使用这个很棒的插件http://grails.org/plugin/cxf-client 来使用具有安全性的合约优先 Web 服务。

所以我的配置中已经有类似的东西了:

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

这非常有效,但我现在想做的是让我的用户能够输入用户名和密码,以便他们可以输入用户名和密码来使用服务。有人知道怎么做吗?

我怀疑它在演示项目中使用了自定义拦截器:

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)
}
}

但由于我没有实例化这个拦截器,也不了解它是如何实例化的,我不知道如何获取拦截器中使用的用户凭据。

有人知道怎么做/有任何示例代码吗?

谢谢!

【问题讨论】:

    标签: grails groovy cxf credentials wss4j


    【解决方案1】:

    假设您使用的是 Spring Security 插件,并且您想要使用的 WS 凭据是您的 User 域对象的属性,那么这样的东西应该可以工作(未经测试):

    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')
      }
    }
    

    并在配置中,将secured = true 替换为securityInterceptor = 'customSecurityInterceptor'

    如果您不使用 Spring Security,同样的模式也可以使用。关键位是回调处理程序

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

    以及getOption中的用户名逻辑

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

    例如,如果用户名和密码存储在 HTTP 会话中,那么您可以使用 Spring RequestContextHolder,而不是 springSecurityService,它的静态 getRequestAttributes() 方法返回当前线程正在处理的 GrailsWebRequest,或者如果当前线程未处理请求(例如,如果它是后台作业),则为 null。

    RequestContextHolder.requestAttributes?.session?.wsUser
    

    或者,如果它们是请求属性(即您在控制器中说过request.wsUser = 'realUsername'),您可以使用RequestContextHolder.requestAttributes?.currentRequest?.wsUser

    【讨论】:

    • 感谢您的回复-但我想出了 Groovier 的做法:D
    • 谢谢 - 如果我只使用工厂来生产特定于会话的 CustomSecurityInterceptors 怎么样?
    • @lilalfyalien 我添加了对RequestContextHolder的更多解释。
    • 如果我的用户名和密码存储在请求对象中,CustomWSS4JOutInterceptor中的getOption()方法在执行什么角色?即为什么我不能在设置密码的同时设置用户名?
    • request 对象/RequestContextHolder 在拦截器的上下文中不存在...我收到“没有此类字段”异常...
    【解决方案2】:

    这是其他人的通用答案:

    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.在 com.company.package 下创建一个 MySecurityInterceptor

    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.现在我们需要将用户名和密码放入请求中(线程安全)以被拦截器拉出:

        import com.company.package.MySecurityInterceptor
        class MySoapSendingController {
    
        SoapProcessor nameOfClient
    
    
        def index() {
    
        request['soapIdentifier'] = "usernameToUse"
        request['soapPassword'] = "passwordToUse"
    
    
                     ...
    
            ReplyMessage replyMsg = nameOfClient.makeSOAPRequest(request)
           }
        }
    

    【讨论】:

    • 这不是线程安全的——如果两个不同的用户同时点击了这个控制器操作,你最终可能会导致其中一个使用另一个凭据,或者更糟的是,他们都试图使用密码一个用户的用户名与另一个用户的用户名...
    • 该死!我没有使用 SpringSecurity,还有其他选择吗?
    • 有没有办法在我的拦截器中维护某种半永久的用户数组,每个用户都有用户名和密码?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多