martedì 14 agosto 2012

Web Service con WS-Security prima parte (SERVER)

Vediamo in questo esempio come realizzare un WS protetto da una username e una password  (in modalità token profile).
In questo post non verrà trattata l'implementazione del Ws, vista in altri post ma solo descritta la procedura di messa in sicurezza. 
Per effettuare una operazione di questo tipo si utilizzano gli Handler soap, che analizzano il contenuto presente nell'header e recuperano li le informazioni necessarie all'autenticazione.


La prima operazione da fare è quella di annotare il nostro Web Service così:

 @HandlerChain(file = "handlers.xml")



Il file handlers.xml definisce l'handler che si occupa di parsare la parte dell'header.

E' definito così:

<?xml version="1.0" encoding="UTF-8"?>
<handler-chains xmlns="http://java.sun.com/xml/ns/javaee">
  <handler-chain>
    <handler>
      <handler-name>it.security.handler.WsSecurityHandler</handler-name>
      <handler-class>it.security.handler.WsSecurityHandler</handler-class>
    </handler>
  </handler-chain>
</handler-chains>

WsSecurityHandler

package it.security.handler;
import it.dao.HeaderParserDao;
import it.daoImpl.HeaderParserDaoImpl;
import it.daoImpl.SecurityDaoImpl;
import it.exception.AuthenticationException;
import it.util.Log;
import java.io.ByteArrayOutputStream;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import javax.xml.namespace.QName;
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;
public class WsSecurityHandler implements SOAPHandler<SOAPMessageContext>
{
      private static final Logger log = Log.get(WsSecurityHandler.class);

      public Set<QName> getHeaders() {
        Set headers = new HashSet();
        QName name = new QName(
          "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd",
          "Security");
        headers.add(name);
        return headers;
      }

      public void close(MessageContext arg0)
      {
      }

      public boolean handleFault(SOAPMessageContext arg0)
      {
        return false;
      }

      public boolean handleMessage(SOAPMessageContext smc)
      {
        
        Boolean outboundProperty =
          (Boolean)smc.get(MessageContext.MESSAGE_OUTBOUND_PROPERTY);
        if (!outboundProperty.booleanValue())
        {
          log.debug("Messaggio in ingresso");
          verificaSicurezza(smc);
        }
        return true;
      }

      private boolean verificaSicurezza(SOAPMessageContext smc) {
        SOAPMessageContext context = smc;
        String username = "";
        boolean verifica = false;
        try {
          ByteArrayOutputStream bas = new ByteArrayOutputStream();
          Set headers = getHeaders();
          Iterator it = headers.iterator();
          while (it.hasNext()) {
            QName q = (QName)it.next();
            log.info("QNAME :" + q.getNamespaceURI());
          }
          context.getMessage().writeTo(bas);
          String envelope = new String(bas.toByteArray(), "UTF-8");
          bas.close();
          log.debug("ENVELOPE:" + envelope);

          if (!envelope.contains("wsse:Security"))
          {
            log.error("Attenzione, il servizio richiede autenticazione");
            throw new AuthenticationException("Attenzione, il servizio richiede autenticazione");
          }

          SecurityDaoImpl aut=SecurityDaoImpl.getInstance();
          HeaderParserDao parser = HeaderParserDaoImpl.getInstance(envelope);
          username = parser.getUserNameFromHeader();
          verifica = aut.verifica(username, parser.getPasswordFromHeader());
        }
        catch (Exception e)
        {
          if (!verifica) throw new AuthenticationException(e);
        }
        if (!verifica) {
          throw new AuthenticationException("Credenziali dell'utente '" + username + "' non corrette.");
        }
        return verifica;
      }

}


HeaderParserDao e HeaderParserDaoImpl sono una classe e una interfaccia che mi sono scritto per effettuare il parsing dell'xml, le riporto per comodità.
La classe SecurityDaoImpl invece si occupa dell'autenticazione (prende in input user e password) e non la riporto in quanto non importante ai fini del post seguente.

HeaderParserDao

public  interface HeaderParserDao
{
  public  String getUserNameFromHeader();

  public  String getPasswordFromHeader();
}

HeaderParserDaoImpl

package it.daoImpl;

import it.dao.HeaderParserDao;
import it.exception.HeaderParserException;

import java.io.StringReader;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.apache.log4j.Logger;
import org.w3c.dom.CharacterData;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import it.util.*;

public class HeaderParserDaoImpl implements HeaderParserDao {

     private static Logger log = Logger.getLogger("MyLogger");
    private static HeaderParserDaoImpl istanza;
      private String xml;
      private String username;
      private String password;

      private HeaderParserDaoImpl(String xml)
      {
        this.xml = xml;
      }

      public static HeaderParserDaoImpl getInstance(String xml) {
        istanza = new HeaderParserDaoImpl(xml);
        istanza.parsaXml();

        return istanza;
      }
    @Override
    public String getUserNameFromHeader() {
        return this.username;
    }

    @Override
    public String getPasswordFromHeader() {
        return this.password;
    }
   
     private void parsaXml() {
            try {
              DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
              DocumentBuilder db = dbf.newDocumentBuilder();
              InputSource is = new InputSource();
              is.setCharacterStream(new StringReader(this.xml));
              Document doc = db.parse(is);
              NodeList nodoSecurity = doc.getElementsByTagName("wsse:Security");
              if (nodoSecurity == null) throw new SecurityException("Attenzione, il servizio richiede autenticazione");
              NodeList nodes = doc.getElementsByTagName("wsse:UsernameToken");
              if (nodes == null) throw new SecurityException("Attenzione, il servizio richiede autenticazione");

              for (int i = 0; i < nodes.getLength(); i++) {
                Element element = (Element)nodes.item(i);

                NodeList name = element.getElementsByTagName("wsse:Username");
                Element line = (Element)name.item(0);
                if (line == null) throw new SecurityException("Attenzione, nell'header soap manca il nodo wsse:Username");
                String username = getCharacterDataFromElement(line);
                log.debug("Username: " + username);
                if (username == null) throw new HeaderParserException("Attenzione manca il nodo wsse:Username nell'header SOAP");
                this.username = username;
                NodeList title = element.getElementsByTagName("wsse:Password");
                line = (Element)title.item(0);
                if (line == null) throw new SecurityException("Attenzione, nell'header soap manca il nodo wsse:Password");
                String password = getCharacterDataFromElement(line);
                this.password = password;
                if (password == null) throw new HeaderParserException("Attenzione manca il nodo wsse:Password nell'header SOAP");
                log.debug("Password: " + password);
              }
            }
            catch (Throwable t) {
              String messaggio = Util.stampaDettaglioEccezione(t);
              log.error(messaggio);
              throw new HeaderParserException("Si è verificato un errore nella  lettura dell'header soap, dettaglio errore: " +
                messaggio);
            }
          }

     private String getCharacterDataFromElement(Element e)
      {
        Node child = e.getFirstChild();
        if ((child instanceof CharacterData)) {
          CharacterData cd = (CharacterData)child;
          return cd.getData();
        }
        return "???formato non leggibile???";
      }

}




Nessun commento:

Posta un commento