Как предоставить динамические учетные данные (имя пользователя и пароль) веб-службе с помощью подключаемого модуля Grails-cxf

Я использую этот замечательный плагин, http://grails.org/plugin/cxf-client, использовать контрактный веб-сервис с безопасностью.

Итак, у меня уже есть что-то подобное в моей конфигурации:

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

Это работает очень хорошо, но сейчас я хотел бы предоставить своим пользователям возможность вводить имя пользователя и пароль, чтобы они могли вводить свое имя пользователя и пароль для использования службы. Кто-нибудь знает как это сделать?

Я подозреваю, что он использует Custom In Interceptor, как в демонстрационном проекте:

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

Но поскольку я не создаю экземпляр этого перехватчика и не понимаю, как он создается, я не знаю, как я могу получить учетные данные пользователя, используемые в перехватчике.

Кто-нибудь знает, как это сделать/есть пример кода?

Спасибо!


person lilalfyalien    schedule 04.12.2012    source источник


Ответы (2)


Предполагая, что вы используете плагин 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, то вместо springSecurityService вы можете использовать Spring RequestContextHolder, чей статический метод getRequestAttributes() возвращает GrailsWebRequest, обрабатываемый текущим потоком, или null, если текущий поток не обрабатывается запрос (например, если это фоновое задание).

RequestContextHolder.requestAttributes?.session?.wsUser

Или, если это атрибуты запроса (т. е. вы сказали request.wsUser = 'realUsername' в контроллере), вы можете использовать RequestContextHolder.requestAttributes?.currentRequest?.wsUser.

person Ian Roberts    schedule 04.12.2012
comment
Спасибо за ваш ответ, но я разработал способ Groovier сделать это: D - person lilalfyalien; 04.12.2012
comment
Спасибо, а как насчет того, чтобы использовать фабрику для создания CustomSecurityInterceptors для конкретных сеансов? - person lilalfyalien; 04.12.2012
comment
@lilalfyalien Я добавил немного больше пояснений к RequestContextHolder. - person Ian Roberts; 04.12.2012
comment
Если мое имя пользователя и пароль хранятся в объекте запроса, какую роль выполняет метод getOption() в CustomWSS4JOutInterceptor? То есть, почему я не могу просто установить имя пользователя одновременно с установкой пароля? - person lilalfyalien; 04.12.2012
comment
Объект request/RequestContextHolder не существует в контексте перехватчика... Я получаю исключения "Нет такого поля"... - person lilalfyalien; 04.12.2012
comment
@lilalfyalien вы добавили соответствующий import? - person Ian Roberts; 04.12.2012
comment
@lilalfyalien Рад, что это помогло. Когда вы получите хороший ответ, решающий вашу проблему, хорошим тоном будет принять его, нажав на галочку слева. Если у вас есть опыт принятия хороших ответов, люди, скорее всего, снова помогут вам в будущем. - person Ian Roberts; 04.12.2012

Вот общий ответ для всех остальных:

<сильный>1. Config.groovy

cxf {
    client {

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

<сильный>2. Ресурсы.groovy

import com.company.package.MySecurityInterceptor

beans = {

nameOfSecurityInterceptorBean(MySecurityInterceptor) {
}

}

<сильный>3. Создайте MySecurityInterceptor в 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. Теперь нам нужно поместить имя пользователя и пароль в запрос (поточно-ориентированный), который будет извлечен перехватчиком:

    import com.company.package.MySecurityInterceptor
    class MySoapSendingController {

    SoapProcessor nameOfClient


    def index() {

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


                 ...

        ReplyMessage replyMsg = nameOfClient.makeSOAPRequest(request)
       }
    }
person lilalfyalien    schedule 04.12.2012
comment
Это не является потокобезопасным - если два разных пользователя одновременно нажимают на это действие контроллера, вы можете в конечном итоге получить, что один из них использует учетные данные другого, или, что еще хуже, они оба пытаются использовать пароль одного пользователя с именем пользователя другого. разное... - person Ian Roberts; 04.12.2012
comment
Черт! Я не использую SpringSecurity, есть ли другие варианты? - person lilalfyalien; 04.12.2012
comment
Есть ли способ поддерживать какой-то полупостоянный массив пользователей в моем перехватчике с именами пользователей и паролями для каждого из них? - person lilalfyalien; 04.12.2012