Mostrando entradas con la etiqueta Jax-WS. Mostrar todas las entradas
Mostrando entradas con la etiqueta Jax-WS. Mostrar todas las entradas

lunes, 15 de junio de 2015

Como agregar un wsee-security y basic-authentication aun webservice con JAX-WS

Después de investigar un problema relacionado con la duplicidad de encabezados en los Handlers al intentar manipular el mensaje SOAP que enviamos mediante un WebService utilizando la libreria JAX-WS, he encontrado al fin una solución que les aliviará los dolores de cabeza con estos errores. 

Seguramente te habrás encontrado la duplicidad de targetnamepsace:

  • S="http://schemas.xmlsoap.org/soap/envelope/" 
  • SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/". 


El posible origen del problema es que estas usando JAX-WS para generar el servicio web (bajo JAX-B) y posteriormente usas SoapHandler (bajo SAAJ) para manipular el mensaje SOAP.

¿Cómo lo sé? La respuesta es que el targetnamepsace S lo genera JAXB y el targetnamepsace SOAP-ENV lo genera SAAJ.

Al usar el SOAPHandler posiblemente estés obteniendo el mensaje de esta forma:
    SOAPMessage msg = SOAPMessageContext.getMessage();
Al obtener el mensaje usando  SOAPMessageContext.getMessage() automaticamente SAAJ intenta crear una cabecera por lo que agrega un header vacío. y en su defecto declara el nuevo targetnamespace SOAP-ENV.

Para resolver el problema, diseñe una clase Helper que te facilitará el trabajo de agregar en JAX-WS la cabecera sin manipular el mensaje. Además verás otras utilidades interesantes que se pueden incluir en el helper.

clase JAXWSHelper
package com.hek.ws.handler;

import java.util.Map;

import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.ws.BindingProvider;

import org.w3c.dom.Document;
import org.w3c.dom.Element;

import com.inetpsa.ped.core.PEDTechnicalException;
import com.sun.xml.ws.api.message.Header;
import com.sun.xml.ws.developer.WSBindingProvider;
import com.sun.xml.ws.message.DOMHeader;

/**
 * Helper class only to use with JAX-WS and useful to obtain a reference to the binding provider and to append different WebServices operations:
 * <p>
 * <ul>
 * <li>basic authentication</li>
 * <li>security headers on paylaod</li>
 * <li>add published url service</li>
 * <li>among others</li>
 * </ul>
 * </p>
 * You can downcast to WSBindingProvider which defines a few more methods provided only by the JAX-WS RI.
 * <p />
 * If you dont use JAX-WS, not use this helper class. You can use javax.xml.ws.handler.soap.SoapHandler to adding security headers
 * 
 * @author E460165
 * @param <S> the BindingProvider generic type
 */
public class JAXWSHelper<S extends BindingProvider> {

    /** The Constant WS_SECEXT. */
    public final static String WS_SECEXT = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd";

    /** The Constant WS_USER_TOKEN_PROFILE. */
    public final static String WS_USER_TOKEN_PROFILE = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText";

    /** The provider. */
    private S provider;

    /**
     * Instantiates a new wss ws security helper.
     * 
     * @param provider the provider
     * @param username the username
     * @param password the password
     */
    public JAXWSHelper(S provider) {
        this.provider = provider;
    }

    /**
     * Adds the http basic authorization.
     */
    public void addHttpBasicAuthorization(String username, String password) {
        Map<String, Object> requestContext = provider.getRequestContext();
        requestContext.put(BindingProvider.USERNAME_PROPERTY, username);
        requestContext.put(BindingProvider.PASSWORD_PROPERTY, password);
    }

    /**
     * Adds the wsse security relates to header and asigned username and password.
     */
    public void addWsseSecurityHeader(String username, String password) {
        try {
            Document document = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
            Element root = document.createElementNS(WS_SECEXT, "wsse:Security");
            document.appendChild(root);

            Element usernameTokenEl = document.createElementNS(WS_SECEXT, "wsse:UsernameToken");
            root.appendChild(usernameTokenEl);

            Element usernameEl = document.createElementNS(WS_SECEXT, "wsse:Username");
            usernameTokenEl.appendChild(usernameEl);
            usernameEl.setTextContent(username);

            Element passwordEl = document.createElementNS(WS_SECEXT, "wsse:Password");
            usernameTokenEl.appendChild(passwordEl);
            passwordEl.setTextContent(password);
            passwordEl.setAttribute("Type", WS_USER_TOKEN_PROFILE);

            Header securityHeader = new DOMHeader<Element>(document.getDocumentElement());

            if (provider instanceof WSBindingProvider) {
                ((WSBindingProvider) provider).setOutboundHeaders(securityHeader);
            }
        } catch (ParserConfigurationException e) {
            throw new PEDTechnicalException("Error adding ws-security header to the webservice", e);
        }
    }

    /**
     * Adds the http endpoint.
     * 
     * @param url the url
     */
    public void addHttpEndpoint(String url) {
        Map<String, Object> requestContext = provider.getRequestContext();
        requestContext.put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY, url);
    }
}
Ejemplo de como utilizar la clase JAXWSHelper
    /** The url service bps. */
    private String URL_SERVICE_BPS;

    /** The wsdl resource. */
    private String WSDL_RESOURCE;

    /** The username. */
    private String USERNAME;

    /** The password. */
    private String PASSWORD;

    private AccesBPSPortType createClientBps() {
        URL wsdlURL = this.getClass().getClassLoader().getResource(WSDL_RESOURCE);

        // call webservice
        WebServiceClient webServiceClient = AccesBPS.class.getAnnotation(WebServiceClient.class);
        AccesBPS accesBps = new AccesBPS(wsdlURL, new QName(webServiceClient.targetNamespace(), webServiceClient.name()));

        AccesBPSPortType client = accesBps.getPort(AccesBPSPortType.class);

        JAXWSHelper jaxWSHelper = new JAXWSHelper(((BindingProvider) client));

        // adding http basic authorization
        jaxWSHelper.addHttpBasicAuthorization(USERNAME, PASSWORD);
        // adding wsse security headers
        jaxWSHelper.addWsseSecurityHeader(USERNAME, PASSWORD);
        // adding httpEndpoint
        jaxWSHelper.addHttpEndpoint(URL_SERVICE_BPS);

        return client;

    }

miércoles, 8 de octubre de 2014

Uso de Jax-WS Handlers para leer encabezados (headers) en los Servicios Web

La semana pasada un compañero de trabajo me comento que no encontraba como leer un encabezado de un mensaje soap mediante el uso de web-services. He decidido compartir esta información para que sea más accesible de encontrar en la web. La respuesta es fácil... usa handlers.

Jax-WS Handlers

El framework JAX-WS (el último API de Java usado para la creación de servicios web basados en SOAP: para publicar y consumir servicios Web) también proporciona un framework Handlers.

Los Handlers (controladores) proporcionan un medio para inspeccionar y manipular los mensajes SOAP entrantes o salientes (tanto en el cliente como en el servidor). Ellos actúan como poderosos interceptores de mensajes que pueden realizar una variedad de funciones tales como la transformación de mensajes, filtrado de contenidos, seguimiento (tracking), etc. De hecho, los handlers se utilizan a menudo en entornos de ejecución para implementar las especificaciones de SOAP y servicios web tales como WS-Security, WS-ReliableMessaging, etc.

Los JAX-WS Handlers son similares a los interceptores EJB o filtros de servlets. Tanto los Handlers, como los interceptores y los filtros, recomiendan a los desarrolladores a seguir la cadena del patrón de responsabilidad.

Tipos de JAX-WS Handlers

JAX-WS ofrece dos tipos de handlers (con dos interfaces): LogicalHandler y SOAPHandler.

  • LogicalHandlers son el protocolo de mensajes neutral. 
  • LogicalHandlers sólo tienen acceso a la carga útil del mensaje de un mensaje. 
  • SOAPHandlers también se conocen como controladores de mensajes o de protocolo. 
  • SOAPHandlers tienen acceso a todo el mensaje SOAP. 
nos enfocaremos en los SoapHandlers.

SOAPHandlers 

La interfaz SOAPHandler requiere que la clase del Handler implemente cuatro métodos:
handleMessage, handleFault, close y getHeaders. 

  • El handleMessage suele ser el método de mayor interés en un handler. El método handleMessage es invocado para el procesamiento normal por el framework como lo es el envío o recepción del mensaje (entrante y saliente). 
  • El método handleFault es invocado por el framework cuando se produce un error de SOAP durante el procesamiento de mensajes. 
  • El método close es invocado por el framework justo antes del envío del mensaje. El método de close permite cualquier limpieza o liberación de recursos al final del procesamiento del handler.

A continuación se muestra un ejemplo de un SOAPHandler (menos el método getHeaders que se explicará a continuación) que se encarga de contar el número de elementos en el encabezado (header) y el cuerpo (body) de un mensaje SOAP. 

//required imports here

public class HeaderHandler implements SOAPHandler {

    @Override 
    public void close(MessageContext context) { 
    }

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

    @Override 
    public boolean handleMessage(SOAPMessageContext messagecontext) { 
        Boolean outbound = (Boolean) messagecontext 
                .get(MessageContext.MESSAGE_OUTBOUND_PROPERTY); 
        if (outbound) { 
            System.out.println("SOAP message departing…"); 
        } else { 
            System.out.println("SOAP message incoming…"); 
        } 
        try { 
            SOAPMessage message = messagecontext.getMessage(); 
            SOAPHeader header = message.getSOAPHeader(); 
            if (header != null) { 
                Iterator i = header.getChildElements(); 
                System.out.println("Number of header elements:  " 
                        + countElements(i)); 
            } 
            SOAPBody body = message.getSOAPBody(); 
            if (body != null) { 
                Iterator i = body.getChildElements(); 
                System.out.println("Number of body elements:  " 
                        + countElements(i)); 
            } 
        } catch (Exception e) { 
            System.out.println(e); 
            e.printStackTrace(); 
        } 
        return true; 
    }

    private int countElements(Iterator i) { 
        int count = 0; 
        while (i.hasNext()) { 
            count++; 
            i.next(); 
        } 
        return count; 
    }
}

El último método, getHeaders, es justo el punto de interés de esta entrada del blog. El método getHeaders recupera los bloques de la cabecera que pueden ser procesados por el handler. No toma ningún parámetro y devuelve un conjunto de nombres completos de encabezado.

El método GetHeaders()

El método getHeaders() obtiene el encabezado soap  (header:soap) así como el tiempo de ejecución del servicio de hosting (alojamiento), de los cuales el handler es el encargado de procesar. Devuelve los QNames del elemento exterior de cada encabezado SOAP que el handler entiende. 

MustUnderstand Not Understood! 


Si has trabajado con los mensajes SOAP, seguramente te habrás dado cuenta que los elementos de header SOAP pueden venir con un atributo global SOAP llamado: mustUnderstand. Los valores válidos para mustUnderstand son true | false (SOAP 1.2) ó 1 | 0 (SOAP 1.1). Este atributo es utilizado para indicar si se requiere o no el receptor o intermediario de servicios web  para entender el elemento de la cabecera antes de procesar el mensaje. 

El SOAPHandler puede servir como un "intermediario" que procesa el encabezado SOAP antes de que el servicio real se encargue de procesar el resto (Body) del mensaje. 

Por ejemplo, a continuación un ejemplo de mensaje SOAP que contiene falsas credenciales de seguridad para ser usado por el lado del servidor para autenticar/autorizar alguna invocación del servicio. Observa donde esta el atributo mustUnderstand entre nombre de usuario (usernameHeader) y contraseña (passwordHeader) de los elementos que tiene la cabecera? 

 
    
      Jim 
      supersecret 
    
    
       
         4 
       
    


si algo no procesa los elementos de la cabecera: nombre de usuario y contraseña, este mensaje podría retornar una falla al cliente. Por ejemplo, supongamos que el servicio no maneja los elementos de la cabecera y el metodo getHeaders del SOAPHandler se codifica como se muestra a continuación.


@Override 
    public Set getHeaders() { 
        return null; 
    }


Dado que nada maneja el usuario y contraseña de la cabecera, el mensaje SOAP resultante dirigido al cliente incluiría un código de fallo MustUnderstand como se muestra aquí

 
    
       
         soap:MustUnderstand 
         MustUnderstand headers: [{urn:com.intertech.secty}password, {urn:com.intertech.secty}username] are not understood. 
       
    


Usando getHeaders() para entender el MustUnderstand


Sin embargo, el método getHeaders () puede ser codificado para indicar al entorno de ejecución que el handler de SOAP se va a encargar de los elementos de la cabecera mustUnderstand devolviendo un conjunto de objetos QName (nombre completo) que coinciden con los elementos de la cabecera mustUnderstand. A continuación, un ejemplo con el nuevo método getHeaders () que maneja el usuario y contraseña de las cabeceras.

@Override 
public Set getHeaders() { 
    System.out.println("Inside SOAP handler of get Headers"); 
    QName securityUsernameHeader = new QName("urn:com.intertech.secty", 
            "username"); 
    QName securityPasswordHeader = new QName("urn:com.intertech.secty", 
            "password"); 
    HashSet headers = new HashSet(); 
    headers.add(securityUsernameHeader); 
    headers.add(securityPasswordHeader); 
    System.out.println("got Headers:  " + headers); 
    return headers; 
}

Ahora, si el mismo elemento SOAP con los elementos de la cabecera: nombre de usuario y contraseña se envía a un servicio, mediante el HeaderHandler del ejemplo, no se genera un fallo. Se supone por el tiempo de ejecución que los elementos de la cabecera fueron consumidos por el handler. Por supuesto, en un mundo real para el SOAP handler, en el método handleMessage () deberías agregar algún código para manejar efectivamente estos encabezados.

Espero que esta información pueda serles de gran utilidad.