Purpose

Manipulating JAXWS header on the server Side like reading WSS username token, logging saop message and publish a specific header.

Introduction

On Telecom IT environment and specially middelware solution web service communications are heavy used between solutions. This tutorial aims to introduce using handler on server side by publishing specific header, reading WSS UserToken or logging the soap message on console. This tutorial is Scala based, Java version can be easy translated.

Parse undeclared custom header

Let’s consider We need to read WSS UserToken non published in our 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>

To parse this header we need to extend a SoapHandler, ovveride handle message class on put header and password on context, below a constant and full class

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
}

Unlike client side we need to add an xml file handler.xml to enable the previous handler

on web service declaration:

  1. Chain this handler : @HandlerChain(file = "handler.xml")
  2. Inject WebServiceContext : @Resource private var wsContext: WebServiceContext = _
  3. Retrieve a messageContext : val msgContext = wsContext.getMessageContext()
  4. Check stored valued : 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
    }
  }
}

Adding custom header

    Publishing a custom header is easier than using the previous handler :
  1. First lets add a custom header class
  2. ``` scala 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= "" } ```
  3. publish this header using @WebParam (name = "usernameToken",header = true)
  4. ``` scala def execute (@WebParam(name = "request" ) request : Request, @WebParam (name = "usernameToken",header = true) header) { .... } ```
  5. retrieving user and password field are the same as accessing class properties :
  6. ``` scala val username = ""+header.getUsername() val password = ""+header.getPassword() ```
  7. note that the preceding header is declared on WSDL method and produce this output on soapUI interface
<soapenv:Header>
      <ws:usernameToken>
         <username>login</username>
         <password>XXXXX</password>
      </ws:usernameToken>
</soapenv:Header>

Conclusion

Publishing specific header using JAX-WS is easier than using handler. Otherwise, handlers on server side are helpful for other purpose like logging I/O message.