sabato 1 giugno 2013

Web Service autenticazione token profile con parsing dell'header tramite SAAJ

Nel seguente esempio vediamo come abilitare la sicurezza in modalità token profile su un Web Service sfruttando per il parsing dell'header le api della libreria SAAJ.
Vediamo il servizio (volutamente banale)

import javax.jws.HandlerChain;
import javax.jws.WebService;
@WebService
@HandlerChain(file="handlers.xml")
public class HelloWorld {
public String saluta(String nome){
 return "Ciao "+nome;
}
}


Possiamo vedere come nell'annotation HandlerChain si specifica il file dove sarà definito l'handler di sicurezza; tale handler provvede a leggere username e password dall'header soap.
L'handler è di tipo SOAPHandler, ossia è in grado di leggere e gestire la sezione di header, deputata all'inserimento dei dati di autenticazione.
Vediamo il codice dell'handler

import java.io.IOException;
import java.util.Iterator;
import java.util.Set;
import java.util.logging.Logger;
import javax.xml.namespace.QName;
import javax.xml.soap.Node;
import javax.xml.soap.SOAPBody;
import javax.xml.soap.SOAPConstants;
import javax.xml.soap.SOAPEnvelope;
import javax.xml.soap.SOAPException;
import javax.xml.soap.SOAPFault;
import javax.xml.soap.SOAPHeader;
import javax.xml.soap.SOAPMessage;
import javax.xml.ws.handler.MessageContext;
import javax.xml.ws.handler.soap.SOAPHandler;
import javax.xml.ws.handler.soap.SOAPMessageContext;
import javax.xml.ws.soap.SOAPFaultException;
/**
 * Controlla user name e password
 * @author 
 *
 */
public class ServerHandler implements SOAPHandler {

 private static final String LoggerName = "ServerSideLogger";
 private static boolean trace=true;
 private Logger logger;
 private final boolean log_p = true; 
 public ServerHandler(){
  logger = Logger.getLogger(LoggerName);
 }
 @Override
 public void close(MessageContext context) {
  
 }

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

 @Override
 public boolean handleMessage(SOAPMessageContext ctx) {
  Boolean response_p = (Boolean)
    ctx.get(MessageContext.MESSAGE_OUTBOUND_PROPERTY);
    // Handle the SOAP only if it's incoming.
    if (!response_p) {
    try {
    SOAPMessage msg = ctx.getMessage();
    SOAPEnvelope env = msg.getSOAPPart().getEnvelope();
    SOAPHeader hdr = env.getHeader();
    String user="";
    String password="";
    // Ensure that the SOAP message has a header.
    if (hdr == null)
    generateSOAPFault(msg, "Attenzione manca la parte header del messaggio soap");
    
    Iterator it =hdr.extractHeaderElements(SOAPConstants.URI_SOAP_ACTOR_NEXT);
    if (it == null || !it.hasNext())
     generateSOAPFault(msg, "Attenzione header non corretto");
     Node next = (Node) it.next();
     if(!"user".equals(next.getNodeName())){
      generateSOAPFault(msg, "attenzione header non corretto");
     }
     else
     {
      user=next.getValue();
      logger.info("Username: "+user);
     }
     if (it == null || !it.hasNext())
      generateSOAPFault(msg, "Attenzione header non corretto"); 
     Node pnode=(Node)it.next();
     if(!"pwd".equals(pnode.getNodeName())){
      generateSOAPFault(msg, "attenzione header non corretto");
     }
     else
     {
      password=pnode.getValue();
     }
     if(!("kermit".equals(user) && "thefrog".equals(password))){
      logger.info("Attenzione errore nelle credenziali di accesso");
      generateSOAPFault(msg, "Attenzione errore nelle credenziali di accesso");
     }
     
     if (trace) msg.writeTo(System.out);
    }
    catch(SOAPException e) { System.err.println(e); }
    catch(IOException e) { System.err.println(e); }
     
    }
    return true;
 }

 @Override
 public Set getHeaders() {
  // TODO Auto-generated method stub
  return null;
 }

 private void generateSOAPFault(SOAPMessage msg, String reason) {
  try {
  SOAPBody body = msg.getSOAPPart().getEnvelope().getBody();
  SOAPFault fault = body.addFault();
  fault.setFaultString(reason);
  throw new SOAPFaultException(fault);
  }
  catch(SOAPException e) { }
  }
}



Nel metodo handle message si recuperano username e password dall'header confrontandole con dei valori cablati nel codice (nella realtà ovviamente ci sarà un invocazione ad un db o un servizio LDAP).
Per pubblicare velocemente il servizio possiamo utilizzare le api della jdk 6, in particolare la classica EndPoint.publish.


Endpoint.publish("http://127.0.0.1:8089/Verifica", new HelloWorld());

Possiamo già testare il servizio con un client tipo SOAP UI. Se lo invochiamo senza inserire nulla nella sezione header avremo il seguente messaggio di errore:

<S:Fault xmlns:ns3="http://www.w3.org/2003/05/soap-envelope">
         <faultcode>S:Server</faultcode>
         <faultstring>Attenzione header non corretto</faultstring>
      </S:Fault>



Se invece effettuiamo una chiamata correttamente, ossia così:

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ser="http://servizio.it/">
   <soapenv:Header>
<user xmlns="http://ch03.fib" xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" SOAP-ENV:actor="http://schemas.xmlsoap.org/soap/actor/next">kermit</user>
<pwd xmlns="http://ch03.fib" xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" SOAP-ENV:actor="http://schemas.xmlsoap.org/soap/actor/next">thefrog</pwd>
</soapenv:Header>
   <soapenv:Body>
      <ser:saluta>
         <!--Optional:-->
         <arg0>carlo</arg0>
      </ser:saluta>
   </soapenv:Body>
</soapenv:Envelope>


La risposta in soap ui è la seguente:

<S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/">
   <S:Body>
      <ns2:salutaResponse xmlns:ns2="http://servizio.it/">
         <return>Ciao carlo</return>
      </ns2:salutaResponse>
   </S:Body>
</S:Envelope>


Vediamo come scrivere ora un client java per il servizio. Dovremo dichiarare un handler che gestisca il messaggio in invio, scrivendo la sezione di autenticazione nell'header.

import java.io.IOException;
import java.util.Set;
import java.util.logging.Logger;
import javax.xml.namespace.QName;
import javax.xml.soap.SOAPConstants;
import javax.xml.soap.SOAPEnvelope;
import javax.xml.soap.SOAPException;
import javax.xml.soap.SOAPHeader;
import javax.xml.soap.SOAPHeaderElement;
import javax.xml.soap.SOAPMessage;
import javax.xml.ws.handler.MessageContext;
import javax.xml.ws.handler.soap.SOAPHandler;
import javax.xml.ws.handler.soap.SOAPMessageContext;
/**
 * Controlla user name e password
 * @author 
 *
 */
public class ClientHandler implements SOAPHandler {

 private static final String LoggerName = "ClientSideLogger";
 private static final String USER_NAME="kermit";
 private static final String PASSWORD="thefrog";
 private Logger logger;
 private final boolean log_p = true; 
 public ClientHandler(){
  logger = Logger.getLogger(LoggerName);
 }
 @Override
 public void close(MessageContext arg0) {
  if (log_p) logger.info("close");  
 }

 @Override
 public boolean handleFault(SOAPMessageContext ctx) {
  if (log_p) logger.info("handleFault");
  try {
  ctx.getMessage().writeTo(System.out);
  }
  catch(SOAPException e) { System.err.println(e); }
  catch(IOException e) { System.err.println(e); }
  return true;
 }

 @Override
 public boolean handleMessage(SOAPMessageContext ctx) {
  if (log_p) logger.info("handleMessage");
  Boolean request_p = (Boolean)
  ctx.get(MessageContext.MESSAGE_OUTBOUND_PROPERTY);
  if (request_p) {
  try {
  SOAPMessage msg = ctx.getMessage();
  SOAPEnvelope env = msg.getSOAPPart().getEnvelope();
  SOAPHeader hdr = env.getHeader();
  if (hdr == null) hdr = env.addHeader();
  QName qname = new QName("http://ch03.fib", "user");
  SOAPHeaderElement helem = hdr.addHeaderElement(qname);
  helem.setActor(SOAPConstants.URI_SOAP_ACTOR_NEXT); // default
  helem.addTextNode(USER_NAME);
  QName pqname=new QName("http://ch03.fib", "pwd");
  SOAPHeaderElement pelem=hdr.addHeaderElement(pqname);
  pelem.setActor(SOAPConstants.URI_SOAP_ACTOR_NEXT);
  pelem.addTextNode(PASSWORD);
  msg.saveChanges();
  msg.writeTo(System.out);
  }
  catch(SOAPException e) { System.err.println(e); }
  catch(IOException e) { System.err.println(e); }
  }
  return true; // continue down the chain
 }

 @Override
 public Set getHeaders() {
  if (log_p) logger.info("getHeaders");
  return null;
 }
}

A questo punto, dopo aver generato il client Java al servizio tramite il comando wsimport, prima di effettuare la chiamata è necessario settare da codice l'handler, sfruttando la proprietà setHandlerResolver della classe HelloWorldService generata dal wsimport, in questo modo:

import it.handlers.ClientHandler;
import java.util.ArrayList;
import java.util.List;
import javax.xml.ws.handler.Handler;
import javax.xml.ws.handler.HandlerResolver;
import javax.xml.ws.handler.PortInfo;
public class CallService {

 public static void  main(String[] args){
  HelloWorldService servizio=new HelloWorldService();
  servizio.setHandlerResolver(new ClientHandlerResolver());
  System.out.println(servizio.getHelloWorldPort().saluta("muttillo"));
 }
}
class ClientHandlerResolver implements HandlerResolver {
 public List getHandlerChain(PortInfo port_info) {
  List hchain = new ArrayList();
  hchain.add(new ClientHandler());
  //hchain.add(new TestHandler()); // for illustration only
  return hchain;
 }
}



Nessun commento:

Posta un commento