Visualizzazione post con etichetta java Web Services. Mostra tutti i post
Visualizzazione post con etichetta java Web Services. Mostra tutti i post

giovedì 10 ottobre 2013

Web Service Java con autenticazione BASIC

Utilizzando JAX-WS, per effettuare una chiamata ad un servizio web protetto con autenticazione BASIC occorre impostare il codice in questo modo:


Authenticator.setDefault(new Authenticator() {
    @Override
    protected PasswordAuthentication getPasswordAuthentication() {
      return new PasswordAuthentication(
        username,
       password.toCharArray());
    }
   });
   // WsTestService s=new WsTestService(urlWsdl);
        Service1 s=new Service1();
        
        Service1Soap ws=s.getService1Soap();
        BindingProvider prov=(BindingProvider)ws;
        prov.getRequestContext().put(BindingProvider.USERNAME_PROPERTY, username);
        prov.getRequestContext().put(BindingProvider.PASSWORD_PROPERTY, password);


Da notare che occorre anteporre l'authenticator perchè il client costruito con JAX-WS effettua una connessione tramite oggetto url prima di invocare le operazioni, quindi bisogna fornire prima i dati di autenticazione e poi dopo in fase di chiamata inserendole nel request context.

Mi è capitato di avere problemi con questo sistema su una installazione avvenuta in ambiente Windows Server 2008.
Ricevevo infatti errore di tipo 401 (Autenticazione non valida) e mettendo un TCP Monitor ho visto che nella richiesta effettuata la modalità di autorizzazione era NTLM invece di BASIC.
Quindi ho aggiunto prima dell'authenticator la seguente riga di codice:


System.setProperty("http.auth.preference", "basic");



sabato 22 giugno 2013

Validazione nel marshalling di un xml

Utilizzando JAXB tipicamente quando effettuiamo l'operazione di lettura dall'xml per popolare i nostri oggetti di business (UNMARSHALLING), si può abilitare la validazione in questo modo:


JAXBContext context=JAXBContext.newInstance("it.esempio");
Unmarshaller um= context.createUnmarshaller();
um.setValidating(true);


Nel caso inverso però (il cosiddetto marshalling) non è disponibile questa feature.
Quindi bisogna utilizzare il seguente costrutto:

Item item=....
JAXBContext context=JAXBContext.newInstance("it.esempio");
Marshaller m= context.createMarshaller();
Validator v=context.createValidator();
v.validate(item);
m.marshal(item,new FileOutputStream("newItem.xml");

domenica 16 giugno 2013

XML Signature

Per la firma digitale di un xml sono disponibili 3 modalità:

ENVELOPING SIGNATURE

L'oggetto da firmare è racchiuso all'interno del contenitore della firma.
Un esempio (preso dal sito Microsoft):

<?xml version="1.0" encoding="UTF-8"?>
<ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
  <ds:SignedInfo>
    <ds:CanonicalizationMethod 
         Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/>
    <ds:SignatureMethod 
         Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>
    <ds:Reference URI="#obj">
      <ds:DigestMethod 
          Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
      <ds:DigestValue/>
    </ds:Reference>
  </ds:SignedInfo>
  <ds:SignatureValue/>
  <ds:Object Id="obj">Hello, World!</ds:Object>
</ds:Signature>


ENVELOPED SIGNATURE

Al contrario dell'enveloping in questo caso è l'oggetto che al suo interno ha un nodo firma. Se ci si pensa è una soluzione più vicina al modello che abbiamo in mente noi quando parliamo di firmare un documento, la firma diventa un attributo del documento.
Sempre dal sito Microsoft un esempio molto calzante, che fa riferimento proprio ad un oggetto di tipo lettera:

<Letter>
   <Return-address>address</Return-address>
   <To>You</To>
   <Message>msg body</Message>
   <From>
      <ds:Signature xmlns:ds="&ds;">
         <ds:SignedInfo>
            <ds:CanonicalizationMethod Algorithm=
                "http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/>
            <ds:SignatureMethod Algorithm=
                "http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>
            <ds:Reference URI="">
               <ds:Transforms>
                  <ds:Transform Algorithm="&enveloped;">
                  </ds:Transform>
               </ds:Transforms>
               <ds:DigestMethod Algorithm="&digest;"/>
               <ds:DigestValue></ds:DigestValue>
           </ds:Reference>
         </ds:SignedInfo>
         <ds:SignatureValue/>
      </ds:Signature>
   </From>
   <Attach>attachement</Attach>
</Letter>
DETACHED SIGNATURE In questo caso la firma è esterna al documento, può essere in un file separato oppure possono comparire entrambe nello stesso file ma come elementi "fratelli" separati:


<internally-detached>
  <ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
    <ds:SignedInfo>
      <ds:CanonicalizationMethod 
         Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/>
      <ds:SignatureMethod 
         Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>
      <ds:Reference URI="#data">
        <ds:DigestMethod 
          Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
        <ds:DigestValue/>
      </ds:Reference>
    </ds:SignedInfo>
    <ds:SignatureValue/>
  </ds:Signature>

  <document Id="data">
     <title>title</title>
     <author>writer</author>
     <date>today</date>
     <content>
        <para>First paragraph</para>
        <para>Second paragraph</para>
     </content>
  </document>
</internally-detached>


sabato 15 giugno 2013

Note sugli Handlers

Gli Handlers possono essere di 2 tipi:

  • SOAPHandler , che implementano l'interfaccia java.xml.ws.handler.SOAPHandler e che hanno accesso sia al payload del messaggio che alla sezione header e a quella degli attachments;
  • LogicalHandler, che implementano l'interfaccia javax.xml.ws.handler.LogicalHandler e che hanno accesso solo al payload del messaggio.
Gli handler sono dichiarati in un file xml, di solito denominato handler-chain.xml (ma possiamo chiamarlo come vogliamo) .
L'ordine di esecuzione cambia a seconda se il messaggio è in uscita o in entrata.
In un messaggio in entrata (incoming message) vengono eseguiti prima i SOAPHandler e poi i logical handler (nell'oridine inverso stabilito nel file xml!!).
In un messaggio in uscita (outgoing message) invece sono prima coinvolti i LogicalHandler e poi i SOAPHandler(nell'ordine stabilito nel file handler-chain.xml).
E' possibile anche filtrare l'esecuzione degli handler secondo determinate condizioni, in particolare nel file handler-chain.xml, prima della lista degli handler, si possono filtrare:

  1. SERVIZI con alcuni name pattern usando l'elemento   javaee:service-name-patter;
  2. PORTE utilizzando javaee:port-name-pattern;
  3. PROTOCOLLI DI BINDING specifici inserendo l'elemento javaee:protocol-bindings

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



sabato 18 maggio 2013

UDDI

L'UDDI (acronimo di Universal Description Discovery and Integration) è un registry (ovvero una base dati ordinata e indicizzata), basato su XML e indipendente dalla piattaforma hardware, che permette alle aziende la pubblicazione dei propri dati e dei servizi offerti su internet. (cit. wikipedia)

  UDDI include un  XML Schema che descrive 4 tipi di informazioni principali:
  • businessEntity (include informazioni sul business name,la descrizione etc);
  • businessService (include informazioni sui web service)
  • bindingTemplate (include informazioni su come e dove accedere il web service)
  • tModel (Technical model, include descrizioni o puntatori a specifiche tecniche esterne oppure a tassonomie).
UDDI può avere come identificativo di un servizio un numero detto Dun & Bradstreet D-U-N-S® Number, un numero di nove cifre codificato secondo un certo standard (vedi qui ).

 Vediamo degli esempi:

businessEntity



<businessEntity
businessKey="0076b468-eb27-42e5-ac09-9955cff462a3"
operator="Microsoft Corporation" authorizedName="Martin Kohlleppel">
<name>Microsoft Corporation</name>
<description xml:lang="en">Empowering people through great software
- any time, any place and on any device is Microsoft's vision. As the
worldwide leader in software for personal and business computing, we
strive to produce innovative products and services that meet our
customer's...
</description>
<contacts>
<contact useType="Corporate Addresses and telephone">
<description xml:lang="en">Corporate Mailing Addresses</
description>
<personName />
<phone useType="Corporate Headquarters">(425) 882-8080</phone>
<address sortCode="~" useType="Corporate Headquarters">
<addressLine>Microsoft Corporation</addressLine>
<addressLine>One Microsoft Way</addressLine>
<addressLine>Redmond, WA 98052-6399</addressLine>
<addressLine>USA</addressLine>
</address>
</contact>
<contact useType="Technical Contact - Corporate UD">
<description xml:lang="en">World Wide Operations</description>
<personName>Martin Kohlleppel</personName>
<email>martink@microsoft.com</email>
</contact>
</contacts>
<identifierBag>
<keyedReference
tModelKey="uuid:8609c81e-ee1f-4d5a-b202-3eb13ad01823"
keyName="D-U-N-S" keyValue="08-146-6849" />
</identifierBag>
<categoryBag>
<keyedReference
tModelKey="uuid:c0b9fe13-179f-413d-8a5b-5004db8e5bb2"
keyName="NAICS: Software Publisher" keyValue="51121" />
</categoryBag>
</businessEntity>


business service / binding Template


<businessService
serviceKey="d5921160-3e16-11d5-98bf-002035229c64"
businessKey="ba744ed0-3aaf-11d5-80dc-002035229c64">
<name>XMethods Delayed Stock Quotes</name>
<description xml:lang="en">20-minute delayed stock quotes</description>
<bindingTemplates>
<bindingTemplate
serviceKey="d5921160-3e16-11d5-98bf-002035229c64"
bindingKey="d594a970-3e16-11d5-98bf-002035229c64">
<description xml:lang="en">
SOAP binding for delayed stock quotes service
</description>
<accessPoint URLType="http">
http://services.xmethods.net:80/soap
</accessPoint>
<tModelInstanceDetails>
<tModelInstanceInfo
tModelKey="uuid:0e727db0-3e14-11d5-98bf-002035229c64" />
</tModelInstanceDetails>
</bindingTemplate>
</bindingTemplates>
</businessService>


tModel 


<tModel
tModelKey="uuid:0e727db0-3e14-11d5-98bf-002035229c64"
operator="www.ibm.com/services/uddi" authorizedName="0100001QS1">
<name>XMethods Simple Stock Quote</name>
<description xml:lang="en">Simple stock quote interface</description>
<overviewDoc>
<description xml:lang="en">wsdl link</description>
<overviewURL>
www.it-ebooks.info
Web Services Essentials
144
http://www.xmethods.net/tmodels/SimpleStockQuote.wsdl
</overviewURL>
</overviewDoc>
<categoryBag>
<keyedReference
tModelKey="uuid:c1acf26d-9672-4404-9d70-39b756e62ab4"
keyName="uddi-org:types" keyValue="wsdlSpec" />
</categoryBag>
</tModel>


Quindi esiste una API per interfacciare UDDI che si divide in 2 macro aree:
  • Inquiry API;
  • Publisher API.
La prima per ricercare servizi UDDI e la seconda invece per pubblicarli o aggiornarli o rimuoverli.
Questa API è basata su SOAP e comprende una serie di funzioni, essitono quindi librerie declinate nel linguaggio di specifico interesse che consentono di effettuare le chiamate al registro UDDI.

Inquiry API

find_binding cerca bindings associati ad uno specifico servizio
find_business cerca un tipo di business che rispetti il criterio specificato
find_service cerca i servizi associati a quello specifico business
find_tModel cerca i technical model che rispettano il criterio specificato
get_bindingDetail per ottenere un completo binding detail
get_businessDetail per ottenere un completo record di una businessEntity
get_serviceDetail per ottenere un completo record di un servizio
get_tModelDetail per ottenere un completo record di un tmodel

Publishing API

get_authToken : richiede un token di autenticazione. Il token verrà richiesto anche per tutte le successive invocazioni
discard_authToken: invalida il token
save_binding Inserisce o aggiorna un bindingTemplate record.
save_business Inserisce o aggiorna un businessEntity record.
save_service Inserisce o aggiorna un businessService record.
save_tModel Inserisce o aggiorna un tModel record.
delete_binding Cancella il  bindingTemplate record specificato dalla bindingKey.
delete_business Cancella il businessEntity record specificato dallabusinessKey.
delete_service Cancella il  businessService record specificagto dalla serviceKey.
delete_tModel nasconde il tModel record specificato dalla tModelKey (non si possono cancellare tModel)



domenica 12 maggio 2013

Mapping tra java class e xsd

Nell'immagine allegata una tabella di riepilogo del mapping tra la  java class e l'xsd:


sabato 11 maggio 2013

Web service REST, non ereditarietà annotation

Nei servizi Web di tipo REST le annotazioni della superclasse valgono nella sottoclasse soltanto se quest'ultima a sua volta non ha annotazioni.
Quindi in questo caso:


public interface Itest
{
@GET@Produces("application/atom+xml")
int mioMetodo();
}
@Path("qwqwqw")
public class MyService implemets Itest
{
@Produces("application/atom+xml")
public int mioMetodo()
{
....
}

}


Le due annotazioni @GET e @Produces dell'interfaccia Itest non sono ereditate dalla classe implementante.

giovedì 25 aprile 2013

Web Service name,portName e serviceName

Tramite annotation è possibile specificare e personalizzare il wsdl.
In particolare l'annotazione @javax.jws.WebService consente di impostare:
  • serviceName - il nome del servizio Web, per capirci quello che si trova come attributo del nodo padre "definitions";
  • name - l'attributo name del nodo portType (sezione contenente i dettagli generali delle operation esposte);
  • portName - l'attributo name del nodo binding (sezione contenente i dettagli specifici delle operation esposte).

venerdì 12 aprile 2013

Web Service gestire zip file

Sotto il codice di realizzazione di un Web Service con un operation che, data in input una directory torna lo zip di tutti i file presenti dentro la directory stessa.
Tralasciando il codice di definizione del Web Service (si tratta di annotare una classe con @WebService e utilizzare @MTOM per ottimizzare il passaggio dati) il metodo è il seguente:

public DataHandler provaDownloadAllegati(String dir) throws Exception
 {
  
 File f=new File(dir);
 File filezip=new File(dir+File.separator+"result.zip");
 ZipOutputStream out = new ZipOutputStream(new FileOutputStream(filezip));
 if(f.isDirectory()){
   String[] fileNames=f.list();
   for(String s: fileNames)
   {
    log.debug("Zippo file "+s);
    if("result.zip".equals(s)){
     continue;
    }
    byte[] b=FileUtil.load(dir+File.separator+s);
    ZipEntry ze=new ZipEntry(s);
    out.putNextEntry(ze);
    out.write(b);
    out.closeEntry();
    
   }
   out.close();
   DataHandler retVal= new DataHandler(new FileDataSource(new File(dir+File.separator+"result.zip")));
   return retVal;
 }
 else
 {
   throw new Exception("Specificare path corretto directory");
 }
  
 }



Ho provato anche ad evitarmi il salvataggio su File System, tornando quindi solo lo stream, ma, pur tornandomi il dato con la size corretta, purtroppo lo stream risulta sempre corrotto, quindi non riesco ad aprirlo.
Posto il codice, non si sa mai se qualcuno ci sia riuscito


......
 ByteArrayOutputStream bos = new ByteArrayOutputStream();
 ZipOutputStream out = new ZipOutputStream(bos);
for(String s:fileNames){
         byte[] allegatoBytes=getBytesFromFileName(dir+file.separator+s);
         out.putNextEntry(new ZipEntry(s));
         out.write(allegatoBytes);
            out.closeEntry();
         }
  return bos.toByteArray();
......

Il metodo getBytesFromFileName è il seguente:


.....
 FileInputStream fin=new FileInputStream(new File(name));
 byte readBuf[] = new byte[512*1024];
   
 try { 
 ByteArrayOutputStream bout = new ByteArrayOutputStream();    
 int readCnt = fin.read(readBuf);
 while (0 < readCnt) {
         bout.write(readBuf, 0, readCnt);
         readCnt = fin.read(readBuf);
 }     
 fin.close();
 return bout.toByteArray();
}
catch(Exception ex ){
.....gestire eccezione......
}


venerdì 15 febbraio 2013

Tomcat 7 errore di tipo javax.xml.ws.soap.SOAPFaultException: javax.xml.ws.WebFault.messageName()Ljava/lang

Sono incappato in questo errore tentando di deployare un web service sotto Tomcat.
Il problema è noto e dovuto al fatto che la jdk utilizzata (1.6.029) ha una versione di jax-ws più vecchia rispetto a quella utilizzata dall'applicativo.
Per fare in modo che Tomcat utilizzi le classi corrette bisogna procedere in questo modo:
  • Da qui scaricare l'ultima versione di JAX.WS;
  • Creare sotto Tomcat7 (a livello della directory lib) una directory endorsed e copiarvi dentro tutti i jar scaricati al passo precedente.
A questo punto gli errori scompaiono.

domenica 10 febbraio 2013

Definire un WebFault in un Web Service

Per usare il meccanismo dei WebFault nei Web Service bisogna procedere in questo modo:
  1. Definire un bean con le proprietà che caratterizzino e definiscano al meglio i dettagli dell'eccezione;
  2. Creare una eccezione annotata con @WebFault avente come proprietà la classe definita al punto 1 e una proprietà definita getFaultInfo() che torni direttamente la classe.

Vediamo il codice.

Bean di dettaglio


package it.exception;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlType;
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "Errore", propOrder = {
    "message","data"
})
public class ErroreLogico  {

 @XmlElement(name="timestamp")
 private String data;
 
 public String getData() {
  return data;
 }

 public void setData(String data) {
  this.data = data;
 }

 private String message;

 public String getMessage() {
  return message;
 }

 public void setMessage(String message) {
  this.message = message;
 }
 

}


WebFault 


package it.exception;
import javax.xml.ws.WebFault;
@WebFault(name="FaultProva",targetNamespace="http://www.esempiws.it")
public class FaultWs extends Exception {
 private ErroreLogico error;
 private static final long serialVersionUID = 1L;
 public FaultWs(String message,ErroreLogico bean){
  super(message);
  this.error=bean;
 }
 public FaultWs(String message,ErroreLogico bean,Throwable cause){
  super(message);
  this.error=bean;
 }
 public ErroreLogico getFaultInfo(){
  return this.error;
 }

}


WebService 


package it.wstest;
import java.text.SimpleDateFormat;
import java.util.Date;
import it.exception.ErroreLogico;
import it.exception.FaultWs;
import javax.jws.WebService;
import javax.jws.soap.SOAPBinding;
import javax.jws.soap.SOAPBinding.ParameterStyle;
import javax.jws.soap.SOAPBinding.Style;
import javax.jws.soap.SOAPBinding.Use;
@WebService(name="MyTest")
@SOAPBinding( style=Style.DOCUMENT,parameterStyle=ParameterStyle.WRAPPED,use=Use.LITERAL)
public class WsProva {
public double dividi(String numeratore,String denominatore) throws FaultWs{
 try
 {
  double num=Double.parseDouble(numeratore);
  double den=Double.parseDouble(denominatore);
  double res=num/den;
  return res;
 }
 catch(Throwable t){
  ErroreLogico e=new ErroreLogico();
  e.setMessage(t.getMessage());
  SimpleDateFormat sdf=new SimpleDateFormat("dd/MM/yyyy hh:mm:ss");
  e.setData(sdf.format(new Date()));
  throw new FaultWs("Errore nella divisione.", e);
 }
}
}


A livello di WSDL troveremo la seguente deinizione:

<types>
<xsd:schema>
<xsd:import namespace="http://www.esempiws.it" schemaLocation="http://localhost:9999/Divisione?xsd=1"/>
</xsd:schema>
<xsd:schema>
<xsd:import namespace="http://wstest.it/" schemaLocation="http://localhost:9999/Divisione?xsd=2"/>
</xsd:schema>
</types>
<message name="dividi">
<part name="parameters" element="tns:dividi"/>
</message>
<message name="dividiResponse">
<part name="parameters" element="tns:dividiResponse"/>
</message>
<message name="FaultWs">
<part xmlns:ns1="http://www.esempiws.it" name="fault" element="ns1:FaultProva"/>
</message>
<portType name="MyTest">
<operation name="dividi">
<input wsam:Action="http://wstest.it/MyTest/dividiRequest" message="tns:dividi"/>
<output wsam:Action="http://wstest.it/MyTest/dividiResponse" message="tns:dividiResponse"/>
<fault message="tns:FaultWs" name="FaultWs" wsam:Action="http://wstest.it/MyTest/dividi/Fault/FaultWs"/>
</operation>
</portType>
<binding name="MyTestPortBinding" type="tns:MyTest">
<soap:binding transport="http://schemas.xmlsoap.org/soap/http" style="document"/>
<operation name="dividi">
<soap:operation soapAction=""/>
<input>
<soap:body use="literal"/>
</input>
<output>
<soap:body use="literal"/>
</output>
<fault name="FaultWs">
<soap:fault name="FaultWs" use="literal"/>
</fault>
</operation>
</binding>
<service name="WsProvaService">
<port name="MyTestPort" binding="tns:MyTestPortBinding">
<soap:address location="http://localhost:9999/Divisione"/>
</port>
</service>
</definitions>


Testando il Ws con soap UI abbiamo la seguente situazione.
 Chiamata:

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:wst="http://wstest.it/">
   <soapenv:Header/>
   <soapenv:Body>
      <wst:dividi>
         <!--Optional:-->
         <arg0>qwe</arg0>
         <!--Optional:-->
         <arg1>12</arg1>
      </wst:dividi>
   </soapenv:Body>
</soapenv:Envelope>


Risposta:

<S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/">
   <S:Body>
      <S:Fault xmlns:ns4="http://www.w3.org/2003/05/soap-envelope">
         <faultcode>S:Server</faultcode>
         <faultstring>Errore nella divisione.</faultstring>
         <detail>
            <ns3:FaultProva xmlns:ns3="http://www.esempiws.it" xmlns:ns2="http://wstest.it/">
               <message>For input string: "qwe"</message>
               <timestamp>10/02/2013 11:36:13</timestamp>
            </ns3:FaultProva>
         </detail>
      </S:Fault>
   </S:Body>
</S:Envelope>


domenica 3 febbraio 2013

SoapBinding style Wrapped oppure Bare

La differenza tra lo style Wrapped oppure Bare si ripercuote sulla generazione del body soap di risposta, non nel WSDL che rimane lo stesso sia se inseriamo l'annotazione Bare piuttosto che Wrapped.
Dato il seguente Web Service:


import javax.jws.WebMethod;
import javax.jws.WebService;
import javax.jws.soap.SOAPBinding;
import javax.jws.soap.SOAPBinding.ParameterStyle;
import javax.jws.soap.SOAPBinding.Style;
import javax.jws.soap.SOAPBinding.Use;

@WebService
@SOAPBinding(style=Style.DOCUMENT,parameterStyle=ParameterStyle.BARE,use=Use.LITERAL)
public class WsTest {
@WebMethod(operationName="test")
public String reverse(){
 return "qaswed";
}
@WebMethod
public String reverse(String s){
 StringBuffer sb=new StringBuffer();
 sb.append(s);
 return sb.reverse().toString();
}
}




In questo caso abbiamo 2 metodi java con lo stesso nome e parametri diversi in input, il classico caso di overloading.
Se non fosse stato specificato un o
Tale Web Service non si riesce a deployare se mettiamo il ParameterStyle a WRAPPED. Questo perchè JAXB scegliendo Wrapped crea dei Wrapper sulla base del nome del metodo e quindi va in conflitto.
Nel caso di annotazione del tipo BARE i wrapper non sono creati.

sabato 19 gennaio 2013

Lettura di un WSDL

Gli elementi descrittivi di un wsdl sono contenuti all'interno del tag definitions.
Vediamoli applicati ad un semplice servizio Web che implementa un solo metodo , il classico hello world.
Di seguito l'estratto del codice Java di generazione del servizio:

@WebService(name="salutatore")
public class Ciao {
 @WebMethod(operationName="helloWorld")
 public String saluta(String nome){
 return "Ciao "+nome;
 }

}


Il WSDL generato è il seguente:

<?xml version='1.0' encoding='UTF-8'?><!-- Published by JAX-WS RI at http://jax-ws.dev.java.net. RI's version is JAX-WS RI 2.2.7-b01  svn-revision#${svn.Last.Changed.Rev}. --><!-- Generated by JAX-WS RI at http://jax-ws.dev.java.net. RI's version is JAX-WS RI 2.2.7-b01  svn-revision#${svn.Last.Changed.Rev}. --><definitions xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" xmlns:wsp="http://www.w3.org/ns/ws-policy" xmlns:wsp1_2="http://schemas.xmlsoap.org/ws/2004/09/policy" xmlns:wsam="http://www.w3.org/2007/05/addressing/metadata" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:tns="http://ws.it/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://schemas.xmlsoap.org/wsdl/" targetNamespace="http://ws.it/" name="CiaoService">
<types>
<xsd:schema>
<xsd:import namespace="http://ws.it/" schemaLocation="http://localhost:8080/WsWebServiceTest/Ciao?xsd=1"/>
</xsd:schema>
</types>
<message name="helloWorld">
<part name="parameters" element="tns:helloWorld"/>
</message>
<message name="helloWorldResponse">
<part name="parameters" element="tns:helloWorldResponse"/>
</message>
<portType name="salutatore">
<operation name="helloWorld">
<input wsam:Action="http://ws.it/salutatore/helloWorldRequest" message="tns:helloWorld"/>
<output wsam:Action="http://ws.it/salutatore/helloWorldResponse" message="tns:helloWorldResponse"/>
</operation>
</portType>
<binding name="salutatorePortBinding" type="tns:salutatore">
<soap:binding transport="http://schemas.xmlsoap.org/soap/http" style="document"/>
<operation name="helloWorld">
<soap:operation soapAction=""/>
<input>
<soap:body use="literal"/>
</input>
<output>
<soap:body use="literal"/>
</output>
</operation>
</binding>
<service name="CiaoService">
<port name="salutatorePort" binding="tns:salutatorePortBinding">
<soap:address location="http://localhost:8080/WsWebServiceTest/Ciao"/>
</port>
</service>
</definitions>

Le sezioni di cui si compone sono le seguenti:

TYPES

Opzionale, definisce i tipi di dato utilizzati.
I dati possono essere tipi semplici (xsd:string etc.) oppure xsd .
La sezione types può contenere direttamente gli xsd oppure puntare ad xsd esterni.
In questo caso l'xsd è linkato:


<xsd:schema><xsd:import namespace="http://ws.it/" 
schemaLocation="http://localhost:8080/WsWebServiceTest/Ciao?xsd=1"/></xsd:schema>


Accedendo all'url si può vedere l'xsd del tipo dato utilizzato.

<?xml version='1.0' encoding='UTF-8'?><!-- Published by JAX-WS RI at http://jax-ws.dev.java.net. RI's version is JAX-WS RI 2.2.7-b01  svn-revision#${svn.Last.Changed.Rev}. --><xs:schema xmlns:tns="http://ws.it/" xmlns:xs="http://www.w3.org/2001/XMLSchema" version="1.0" targetNamespace="http://ws.it/">
<xs:element name="helloWorld" type="tns:helloWorld"/>
<xs:element name="helloWorldResponse" type="tns:helloWorldResponse"/>
<xs:complexType name="helloWorld">
<xs:sequence>
<xs:element name="arg0" type="xs:string" minOccurs="0"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="helloWorldResponse">
<xs:sequence>
<xs:element name="return" type="xs:string" minOccurs="0"/>
</xs:sequence>
</xs:complexType>
</xs:schema>


MESSAGES

Sono associati ai tipi e rappresentano per così dire i parametri di ingresso e di uscita delle operazioni dei servizi web (che sono quindi 2, vedi xsd sopra definito).

<message name="helloWorld">
<part name="parameters" element="tns:helloWorld"/>
</message>
<message name="helloWorldResponse">
<part name="parameters" element="tns:helloWorldResponse"/>
</message>


PORTTYPE 

Il PortType rappresenta l'interfaccia del metodo richiamato, si noti come il nome del port type sia il name del servizio Web mentre nel nodo operation abbiamo il censimento dell'unica operazione ad esso associata, denominata helloworld.

BINDING

Questa sezione, sempre utilizzando una sintassi "javista" rappresenta l'implementazione del servizio o dei servizi definiti nella sezione PORTTYPE.
 In particolare fornisce informazioni su:
  • Il protocollo di trasporto utilizzato, che può essere di tipo HTTP o SMTP. Dall'attributo transport si evince in questo caso che il protocollo utilizzato è HTTP;
  • Lo style del servizio, che può essere di tipo RPC oppure DOCUMENT (document è il default);
  • Il formato dati utilizzato nel messaggio SOAP,  anche qui ci sono due possibilità, literal (come in questo caso, si tratta  puro e semplice xml) oppure encoded (messaggio xml conforme a regole esterne). Si noti che il  formato encoded non è WS-I compliant.
SERVICE

La sezione service direttamente l'endpoint dove è disponibile il servizio.
 

Accedere ad un parametro del web.xml in un Web Service

Dato un Web Service realizzato usando jax-ws 2.0 (vedi esempio qui) per accedere ad un parametro definito nel web.xml come un context-param si utilizza l'interfaccia javax.xml.ws.WebServiceContext.
Di seguito l'esempio di inejction sulla classe che implementa il Web Service:


@Resource
private WebServiceContext context;

Per accedere al parametro invece:

ServletContext ctx=(ServletContext)context.getMessageContext().get(MessageContext.SERVLET_CONTEXT);
String param=ctx.getInitParameter("STAGE");



martedì 6 novembre 2012

Web Service Rest

In questo post vediamo come realizzare un semplice servizio Web Rest e deployarlo come una Web App sotto Tomcat 7.
Innanzitutto qui possiamo trovare le basi teoriche dei servizi Web Rest.
Tra i vantaggi dell'utilizzo di un servizio Web Rest c'è l'eliminazione del payload soap e quindi una maggiore velocità di fruizione.
Tra gli svantaggi per adesso sicuramente c'è quello di non avere un sistema built-in di security integrato (tipo WS-Security per i Web Service SOAP).
Sviluppare l'esempio di per se è stato molto semplice, l'unico problema è quello di avere le giuste dipendenze nel classpath.
Ho utilizzato le librerie di Jersey per costruire il servizio.
In particolare dentro la WEB-INF /lib ho i seguenti jar:
  • asm-3.3.1.jar;
  • jaxrs-api.jar;
  • jersey-core-1.1.5.1.jar;
  • jersey-server-1.1.5.1.jar;
  • jsr311-api-1.1.jar.
Le librerie di Jersey si trovano sul sito, la jsr311 l'ho scaricata da internet direttamente.
Nel web.xml dobbiamo configurare la servlet di Jersey per esporre il servizio.

<servlet>
    <servlet-name>RestfulContainer</servlet-name>
    <servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class>
    <init-param>
      <param-name>com.sun.jersey.config.property.packages</param-name>
      <param-value>it.test</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
  </servlet>
  <servlet-mapping>
    <servlet-name>RestfulContainer</servlet-name>
    <url-pattern>/rest/*</url-pattern>
  </servlet-mapping> 

Si noti che il param-value rappresenta il nome del package in cui abbiamo deployato il servizio.

La classe che implementa il servizio Web è la seguente:

package it.test;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
// The Java class will be hosted at the URI path "/helloworld"
@Path("/helloworld")
public class RestTest {
    
  // This method is called if TEXT_PLAIN is request
   @GET
   @Produces(MediaType.TEXT_PLAIN)
   public String sayPlainTextHello() {
     return "Hello Jersey";
   }

   // This method is called if XML is request
   @GET
   @Produces(MediaType.TEXT_XML)
   public String sayXMLHello() {
     return "" + "<hello> Hello Jersey" + "</hello>";
   }

   // This method is called if HTML is request
   @GET
   @Produces(MediaType.TEXT_HTML)
   public String sayHtmlHello() {
     return "<html> " + "<title>" + "Hello Jersey" + "</title>"
         + "<body><h1>
<b>" + "Hello Gino" + "<b></b></b></h1>
</body></html></code></pre>
" + " ";
   }
   @GET
   @Path("/greetings/{test}")
    @Produces(MediaType.TEXT_PLAIN)
   public String saluta(@PathParam("test") String t){
    return "Ciao "+t;
   }
  
}  

Sono servizi in get molto semplici, con l'annotazione @Produces si specifica il tipo di output che sarà prodotto mentre con @PathParam si indica un parametro da passare in input al servizio (è il caso del metodo saluta).

Una volta fatto partire il server possiamo verificare che il servizio risponda digitando

http://localhost:8080/TestRest/rest/helloworld

Dovrebbe apparire una pagina bianca con scritto "Hello Gino".
 Poichè nei servizi rest una risorsa è indentificata dal suo URI aggiungendo al path della web app +path servlet il nome specificato nell'annotazione @Path della classe.

Scrivere un client Java al servizio

Ho creato un semplice java project inserendo le seguenti librerie nel classpath:

  • asm-3.3.1.jar;
  • jaxrs-api.jar;
  • jersey-bundle-1.9.1.jar.
Il codice del client è il seguente

package it.client;
import java.net.URI;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.UriBuilder;
import com.sun.jersey.api.client.Client;
import com.sun.jersey.api.client.ClientResponse;
import com.sun.jersey.api.client.WebResource;
import com.sun.jersey.api.client.config.ClientConfig;
import com.sun.jersey.api.client.config.DefaultClientConfig;
public class ClientTest {
 public static void main(String[] args) {
      ClientConfig config = new DefaultClientConfig();
      Client client = Client.create(config);
      WebResource service = client.resource(getBaseURI());
      // Fluent interfaces
      System.out.println(service.path("rest").path("helloworld").accept(MediaType.TEXT_PLAIN).get(ClientResponse.class).toString());
      // Get plain text
      System.out.println(service.path("rest").path("helloworld").accept(MediaType.TEXT_PLAIN).get(String.class));
      // Get XML
      System.out.println(service.path("rest").path("helloworld").accept(MediaType.TEXT_XML).get(String.class));
      // The HTML
      System.out.println(service.path("rest").path("helloworld").accept(MediaType.TEXT_HTML).get(String.class));
     System.out.println(service.path("rest").path("helloworld/greetings/kjasdkjasd").type(MediaType.TEXT_PLAIN).get(String.class)); 

 }
  private static URI getBaseURI() {
      return UriBuilder.fromUri("http://localhost:8080/TestRest").build();
    }
}

L'output prodotto a console è il seguente:

GET http://localhost:8080/TestRest/rest/helloworld returned a response status of 200 OK
Hello Jersey
<hello> Hello Jersey</hello>
<html> <title>Hello Jersey</title><body><h1>
<b>Hello Gino<b></b></b></h1>
</body></html>
Ciao kjasdkjasd

martedì 14 agosto 2012

Web Service con WS-Security seconda parte (CLIENT)

Per generare un client di un Web Service messo in sicurezza come visto nel precedente post occorre per prima cosa tramite l'utility wsimport (vedi qui ) generare le classi del client e dopo aggiungere l'handler che si occuperà in questo caso di scrivere username e password nell'header soap.

Ho scritto 2 classi una è l'implementazione dell'HandlerResolver usati in JAX-WS 2.0 proprio per "prendere il controllo" della eventuale catena di Handler presenti (in questo caso sarà soltanto uno).

Poi invece bisogna scrivere l'handler specifico che implementerà al solito l'interfaccia SOAPHandler.


MyHandler

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;
import javax.xml.ws.handler.soap.SOAPHandler;
public class MyHandler implements HandlerResolver {

    @Override
    public List<Handler> getHandlerChain(PortInfo portInfo) {
        List<Handler> handlerList = new ArrayList<Handler>();
        SOAPHandler handler =  new WSSecurityHandler();
        handlerList.add(handler);
        return handlerList;
    }

}

WsSecurityHandler

package it.handler;

import java.io.IOException;
import java.util.Set;
import java.util.TreeSet;

import javax.xml.namespace.QName;
import javax.xml.soap.SOAPElement;
import javax.xml.soap.SOAPEnvelope;
import javax.xml.soap.SOAPException;
import javax.xml.soap.SOAPFactory;
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;
/**
 * Handler che gestisce il passaggio <br>
 * di username e password nell'header soap
 * @author
 *
 */
public class WSSecurityHandler implements SOAPHandler {
    private boolean scriviOutputSuConsole=true;
    /**
     * INSERIRE LO USERNAME
     */
    public static final String USER_NAME="pippo";
    /**
     * INSERIRE LA PWD 
     */
    public static final String PWD="pluto";
    @Override
    public void close(MessageContext arg0) {
        if(scriviOutputSuConsole) System.out.println("CLOSE");
    }

    @Override
    public boolean handleFault(MessageContext context1) {
        SOAPMessageContext context = (SOAPMessageContext)context1;
        try {
            if(scriviOutputSuConsole)context.getMessage().writeTo(System.out);
        } catch (SOAPException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return false;
    }

    @Override
 
    public boolean handleMessage(MessageContext context1) {
        SOAPMessageContext context = (SOAPMessageContext)context1;
        Boolean outbound = (Boolean)context.get(MessageContext.MESSAGE_OUTBOUND_PROPERTY);
        if (outbound.booleanValue())
        {
            try {
                SOAPEnvelope envelope = context.getMessage().getSOAPPart().getEnvelope();
                SOAPFactory factory = SOAPFactory.newInstance();
                String prefix = "wsse";
                String uri = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd";
                SOAPElement securityElement = factory.createElement("Security", prefix, uri);
                QName nameMust = new QName(envelope.getPrefix()+":mustUnderstand");
                securityElement.addAttribute(nameMust, "1");
                securityElement.addNamespaceDeclaration("wsu", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd");
                SOAPElement tokenElement = factory.createElement("UsernameToken", prefix, uri);
                QName nameWSU = new QName("wsu:Id");
                tokenElement.addAttribute(nameWSU, "token-1-1236072936329-25515818");
                SOAPElement username = factory.createElement("Username", prefix, uri);
                username.addTextNode(USER_NAME);
                SOAPElement pwd = factory.createElement("Password", prefix, uri);
                pwd.addTextNode(PWD);
                tokenElement.addChildElement(username);
                tokenElement.addChildElement(pwd);
                securityElement.addChildElement(tokenElement);
                SOAPHeader header = envelope.addHeader();
                header.addChildElement(securityElement);
                if(scriviOutputSuConsole) context.getMessage().writeTo(System.out);
               
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        else
        {
        }
        return true;
    }

    @Override
    public Set getHeaders() {
        return new TreeSet();
    }
}



A questo punto dal nostro client Java dobbiamo scrivere il seguente codice (supponiamo di aver messo in sicurezza il Web Service visto in questo post )

FattorialeDaoImplService f=new FattorialeDaoImplService();
CalcoloWs port=f.getCalcoloWsPort();
HandlerResolver resolver = new MyHandler();
f.setHandlerResolver(resolver);    
         
long fatt=port.getFattoriale(5);                 
System.out.println(fatt);



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???";
      }

}