Ouertani Slim was born in Tunisia in 1981. Now he is a software engineer since 2004 and he is Java 6 and Spring certified with 100% and 94% respectively. He is very interested in Java technology, Scala and open source projects. He believes that programming is one of the hardest jobs and most beautiful in the world. Slim has posted 32 posts at DZone. You can read more from them at their website. View Full User Profile

Jaxws Header : Part 2 : Server Side

06.25.2012
| 9868 views |
  • submit to reddit
Purpose
Manipulating JAXWS header on the server Side like reading WSS username token, logging saop message and publish a specific header.

IntroductionOn 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 headerLet'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 reader 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
<?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>

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 headerPublishing a custom header is easier than using the previous handler :

1-First lets add a custom header class
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 - publish this header using @WebParam (name = "usernameToken",header = true)
def execute (@WebParam(name = "request" ) request : Request, @WebParam (name = "usernameToken",header = true) header) {
  ....
 }

3- retrieving user and password field are the same as accessing class properties :
    val username = ""+header.getUsername()
    val password = ""+header.getPassword()
4- 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.
Published at DZone with permission of its author, Slim Ouertani.

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)

Comments

Lord Foom replied on Fri, 2012/11/09 - 5:12am


Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.