Статьи

Заголовок Jaxws: Часть 2: Сторона сервера

Цель

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

Вступление

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

Этот учебник основан на Scala, Java-версия может быть легко переведена.

Разобрать необъявленный пользовательский заголовок

Давайте рассмотрим, что нам нужно прочитать WSS UserToken, не опубликованный в нашем WSDL:

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

Для разбора этого читателя нам нужно расширить класс сообщения SoapHandler, ovveride handle для заголовка и пароля для контекста, ниже константы и полного класса

object AuthenticationHandlerConstants {

  val REQUEST_USERID: String = "authn_userid";
  val REQUEST_PASSWORD: String = "authn_password";
  val AUTHN_PREFIX: String = "wsse";
  val AUTHN_LNAME: String = "Security";
  val AUTHN_URI: String = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd";
  val AUTHN_STAUTS: String = "authnStatus";

  val LOGIN: String = "login"
  val PWD: String = "passwd"
}

import java.io.PrintStream;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import javax.xml.namespace.QName;
import javax.xml.soap.SOAPElement;

import javax.xml.soap.SOAPHeader;
import javax.xml.ws.handler.MessageContext;
import javax.xml.ws.handler.soap.SOAPHandler;
import javax.xml.ws.handler.soap.SOAPMessageContext;
import org.apache.log4j.Logger;

/**
 * @author Slim Ouertani
 */
class SecuritySOAPHandler extends SOAPHandler[SOAPMessageContext] {

  private val L = Logger.getLogger(classOf[SecuritySOAPHandler]);

  override def getHeaders(): Set[QName] = {
    val securityHeader = new QName("http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd", "Security", "wsse");
    val headers = new HashSet[QName]();
    headers.add(securityHeader);
    return headers;
  }

  override def handleMessage(soapMessageContext: SOAPMessageContext): Boolean = {

    try {
      val outMessageIndicator = soapMessageContext.get(MessageContext.MESSAGE_OUTBOUND_PROPERTY).asInstanceOf[Boolean];
      if (!outMessageIndicator) {
        val envelope = soapMessageContext.getMessage().getSOAPPart().getEnvelope()
        val header = envelope.getHeader()

        if (header == null) {
          L.warn("No headers found in the input SOAP request")
        } else {
          processSOAPHeader(header) match {
            case Some(_processSOAPHeader) => {
              soapMessageContext.put(AuthenticationHandlerConstants.AUTHN_STAUTS, java.lang.Boolean.TRUE);
              soapMessageContext.put(AuthenticationHandlerConstants.LOGIN, _processSOAPHeader._1);
              soapMessageContext.put(AuthenticationHandlerConstants.PWD, _processSOAPHeader._2);
            }
            case None => {
              soapMessageContext.put(AuthenticationHandlerConstants.AUTHN_STAUTS, java.lang.Boolean.FALSE);
              soapMessageContext.put(AuthenticationHandlerConstants.LOGIN, "");
              soapMessageContext.put(AuthenticationHandlerConstants.PWD, "");
            }
          }
        }
      }
    } catch {
      case ex: Exception => L.error(ex.getMessage(), ex);
    }
    soapMessageContext.setScope(AuthenticationHandlerConstants.AUTHN_STAUTS, MessageContext.Scope.APPLICATION);
    soapMessageContext.setScope(AuthenticationHandlerConstants.LOGIN, MessageContext.Scope.APPLICATION);
    soapMessageContext.setScope(AuthenticationHandlerConstants.PWD, MessageContext.Scope.APPLICATION);
    return true;
  }

  private def processSOAPHeaderInfo(e: SOAPElement): (String,String) = {
    var _id: String = null
    var _password: String = null

    val childElements = e.getChildElements(new QName(AuthenticationHandlerConstants.AUTHN_URI, "UsernameToken"));
    while (childElements.hasNext()) {
      val usernameToken = childElements.next();
      // loop through child elements

      usernameToken match {

        case child: SOAPElement => {

          val childElements1 = child.getChildElements(new QName(AuthenticationHandlerConstants.AUTHN_URI, "Username"));
          val childElements2 = child.getChildElements(new QName(AuthenticationHandlerConstants.AUTHN_URI, "Password"));

          while (childElements1.hasNext()) {
            val next = childElements1.next();
            next match {
              case l: SOAPElement => {
                val value = l.getValue();

                _id = value;
              }
            }
          }
          while (childElements2.hasNext()) {
            val next = childElements2.next();
            next match {
              case l: SOAPElement => {
                val value = l.getValue()

                _password = value;
              }
            }
          }
        }
      }
    }
    return (_id, _password);
  }

  private def processSOAPHeader(sh: SOAPHeader): Option[(String,String)] = {
    var authenticated: Option[(String,String)] = None

    val childElems = sh.getChildElements(new QName(
      AuthenticationHandlerConstants.AUTHN_URI,
      AuthenticationHandlerConstants.AUTHN_LNAME));

    // iterate through child elements
    while (childElems.hasNext()) {

      val child = childElems.next().asInstanceOf[SOAPElement]
      authenticated = Some(processSOAPHeaderInfo(child))
    }
    return authenticated
  }

  override def handleFault(soapMessageContext: SOAPMessageContext): Boolean = false
}

В отличие от клиентской стороны нам нужно добавить xml-файл
handler.xml, чтобы включить предыдущий обработчик

<?xml version="1.0" encoding="UTF-8"?>
<handler-chains xmlns="http://java.sun.com/xml/ns/javaee">
    <handler-chain>
        <handler>
            <handler-name>SecuritySOAPHandler</handler-name>
            <handler-class>me.ouertani.slim.SecuritySOAPHandler</handler-class>
        </handler>
    </handler-chain>
</handler-chains>

в декларации веб-сервиса:

  1. Цепочка этого обработчика:   @HandlerChain (file = «handler.xml»)
  2. Вставить WebServiceContext:   @Resource private var wsContext: WebServiceContext = _
  3. Получить сообщениеContext:   val msgContext = wsContext.getMessageContext ()
  4. Проверьте сохраненные значения:   val authnStatus = msgContext.get (AuthenticationHandlerConstants.AUTHN_STAUTS)

@WebService(serviceName = "WsService", targetNamespace = "http://slim.ouertani.me)
@HandlerChain(file = "handler.xml")
@Stateless()
@Interceptors(Array(classOf[TracingInterceptor]))
class WsService extends LogHelper{
   
  @Resource private var wsContext: WebServiceContext = _

  def authenticate() {

    val msgContext = wsContext.getMessageContext()
    val authnStatus = msgContext.get(AuthenticationHandlerConstants.AUTHN_STAUTS)
    if (authnStatus == null || !authnStatus.asInstanceOf[Boolean]) {
       L.warn("header authentification not found");
      throw AuthenticationHeaderNotFound.toFaultResponse
    }

    try {
      val login = msgContext.get("login").asInstanceOf[String]
      val passwd = msgContext.get("passwd").asInstanceOf[String]
      L.info("user " + login + "pwd" + passwd + "try this service")
    } catch {
      case ex: Exception =>
        L.warn(" exception to parse header authentification", ex);
        throw AuthenticationHeaderNotFound.toFaultResponse
    }
  }
}

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

Публикация пользовательского заголовка проще, чем использование предыдущего обработчика:
1-First позволяет добавить пользовательский класс заголовка



import scala.reflect.BeanProperty
import javax.xml.bind.annotation.{XmlType , XmlAccessType, XmlElement, XmlAccessorType};
/**
 * @author Slim Ouertani
 */
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "Header", propOrder = Array(
    "username",
    "password"
  ))
class UsernameToken {
   @BeanProperty  @XmlElement(required = true, nillable = false ) var username : String= ""
   @BeanProperty  @XmlElement(required = true, nillable = false ) var password : String= ""
}


2 — опубликовать этот заголовок, используя

@WebParam (name = «usernameToken», header = true)

def execute (@WebParam(name = "request" ) request : Request, @WebParam (name = "usernameToken",header = true) header) {
  ....
 }

3- извлечение поля пользователя и пароля аналогично доступу к свойствам класса:

    val username = ""+header.getUsername()
    val password = ""+header.getPassword()

4 — обратите внимание, что предыдущий заголовок объявлен в методе WSDL и выдает этот вывод на интерфейс soapUI

<soapenv:Header>
      <ws:usernameToken>
         <username>login</username>
         <password>XXXXX</password>
      </ws:usernameToken>
</soapenv:Header>

Вывод

Публикация определенного заголовка с использованием JAX-WS проще, чем с использованием обработчика. В противном случае обработчики на стороне сервера полезны для других целей, таких как регистрация сообщений ввода / вывода.