Статьи

Заголовок JAX-WS: Часть 1 на стороне клиента

Цель

Управление заголовком JAXWS на стороне клиента, например добавление токена имени пользователя WSS или регистрация сообщения saop.

Вступление

В телекоммуникационной ИТ-среде и специально для промежуточного программного обеспечения мы редко выполняем всю работу, а скорее делегируем некоторые бизнес-процессы другим уровням. Связь между веб-сервисами широко используется между решениями. Цель этого руководства — познакомиться с использованием обработчика на стороне клиента путем добавления WSS UserToken или регистрации сообщения мыла на консоли.

Добавление необъявленного пользовательского заголовка

Некоторому клиенту Ws необходимо добавить пользовательский заголовок, который не объявлен в WSDL. Добавление токена имени пользователя WSS похоже на добавление этого фрагмента XML в элемент заголовка:

<soapenv:Header>
      <wsse:Security  xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
         <wsse:UsernameToken wsu:Id="UsernameToken-1">
            <wsse:Username>login</wsse:Username>
            <wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText">XXXX</wsse:Password>        
         </wsse:UsernameToken>
      </wsse:Security>
 </soapenv:Header>

В отличие от LogicalHandler SOAPHandler имеет доступ ко всему сообщению Soap. Давайте создадим локальный компонент без сохранения состояния WSSUsernameTokenSecurityHandler, расширяющий SOAPHandler для создания предыдущего заголовка.
Ниже класса WSSUsernameTokenSecurityHandler

package me.slim.ouertani;

import java.util.Set;
import java.util.TreeSet;
import javax.annotation.Resource;
import javax.ejb.LocalBean;
import javax.ejb.Stateless;
import javax.xml.namespace.QName;
import javax.xml.soap.*;
import javax.xml.ws.handler.MessageContext;
import javax.xml.ws.handler.soap.SOAPHandler;
import javax.xml.ws.handler.soap.SOAPMessageContext;

@Stateless
@LocalBean
public class WSSUsernameTokenSecurityHandler implements SOAPHandler<SOAPMessageContext> {

    @Resource(lookup = "login")
    private String login;
    @Resource(lookup = "pwd")
    private String pwd;

    public WSSUsernameTokenSecurityHandler() {
    }

    @Override
    public boolean handleMessage(SOAPMessageContext context) {

        Boolean outboundProperty =
                (Boolean) context.get(MessageContext.MESSAGE_OUTBOUND_PROPERTY);
        if (outboundProperty.booleanValue()) {

            try {
                SOAPEnvelope envelope = context.getMessage().getSOAPPart().getEnvelope();
                SOAPFactory factory = SOAPFactory.newInstance();
                String prefix = "wsse";
                String uri = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd";
                SOAPElement securityElem =
                        factory.createElement("Security", prefix, uri);
                SOAPElement tokenElem =
                        factory.createElement("UsernameToken", prefix, uri);
                tokenElem.addAttribute(QName.valueOf("wsu:Id"), "UsernameToken-2");
                tokenElem.addAttribute(QName.valueOf("xmlns:wsu"), "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd");
                SOAPElement userElem =
                        factory.createElement("Username", prefix, uri);
                userElem.addTextNode(login);
                SOAPElement pwdElem =
                        factory.createElement("Password", prefix, uri);
                pwdElem.addTextNode(pwd);
                pwdElem.addAttribute(QName.valueOf("Type"), "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText");
                tokenElem.addChildElement(userElem);
                tokenElem.addChildElement(pwdElem);
                securityElem.addChildElement(tokenElem);
                SOAPHeader header = envelope.addHeader();
                header.addChildElement(securityElem);

            } catch (Exception e) {
                e.printStackTrace();
            }
        } else {
            // inbound
        }
        return true;
    }

    @Override
    public Set<QName> getHeaders() {
        return new TreeSet();
    }

    @Override
    public boolean handleFault(SOAPMessageContext context) {
        return false;
    }

    @Override
    public void close(MessageContext context) {
        //
    }
}

Затем объявите StatelessBean и введите оба Webservice через @WebServiceRef и WSSUsernameTokenSecurityHandler через @EJB. Метод init обратного вызова добавит HandlerResolver к службе. Ниже полная реализация

@Stateless
@LocalBean
public class WebServiceClientBean {

    @WebServiceRef()
    private WsService service;
    @EJB
    private WSSUsernameTokenSecurityHandler wSSUsernameTokenSecurityHandler;

    @PostConstruct
    private void init() {
        service.setHandlerResolver( new HandlerResolver() {

            @Override
            public List<Handler> getHandlerChain(PortInfo portInfo) {
                List<Handler> handlerList = new ArrayList<Handler>();
                handlerList.add(wSSUsernameTokenSecurityHandler);
                return handlerList;
            }
        });
    }

  public WsResponse getService(WsRequest wsRequest) {      
        WsPort port = service.getPort();
        return port.invoqueService(wsRequest);
    }

}

Регистрация сообщений SOAP

Ведение журнала обмена XML-сообщениями также может быть с использованием обработчиков, ниже обработчика фрагмента scala, используемого для входа и выхода сообщений:

class RequestResponsePrinter  extends SOAPHandler[SOAPMessageContext] with LogHelper {

override def getHeaders(): Set[QName] = {
    return new TreeSet();
  }

  override def handleMessage(context: SOAPMessageContext): Boolean = {
    val sb = new ToStringBuilder(this).append("Call operation", "handleMessage");

    L.debug(sb);
    val outboundProperty = context.get(MessageContext.MESSAGE_OUTBOUND_PROPERTY).asInstanceOf[Boolean]


      try {
            val msg = context.getMessage()
            val out = new ByteArrayOutputStream();
            msg.writeTo(out);
            val strMsg = new String(out.toByteArray());
            L.debug("outbound  : "+outboundProperty.booleanValue()+" [" + strMsg + "]");

      } catch {
        case e: Exception =>
          L.error(sb.append("EXCEPTION", e.getMessage()), e);
      }

    return true;
  }

  override def handleFault(context: SOAPMessageContext): Boolean = {
    return false;
  }

  override def close(context: MessageContext) {
    //
  }
}
  • Scala-версия обработчика обработчиков
new HandlerResolver() {

      override def getHandlerChain(portInfo: PortInfo): java.util.List[javax.xml.ws.handler.Handler[_ <: javax.xml.ws.handler.MessageContext]] = {
        import scala.collection.mutable.ArrayBuffer
        val handlerList = ArrayBuffer[Handler[_ <: javax.xml.ws.handler.MessageContext]]()       
        handlerList += new RequestResponsePrinter()
        return handlerList

      }
    };

Вывод

Мы интенсивно использовали обработчик на стороне клиента. На стороне сервера все может немного измениться, продолжение следует …