23 noviembre 2013

Apache CXF: tutorial de invocación de servicios web

Hace algunos meses, en un proyecto en iSOCO nos surgió la necesidad de enlazar con servicios web proporcionados por otra aplicación, y uno de los requerimientos era utilizar la librería Apache CXF. Últimamente, habíamos trabajado con Apache Axis, así que dedicamos unos días a buscar y procesar información sobre CXF.

Después de leer muchos artículos, separar el grano de la paja y unificar mucha información dispersa, elaboramos este tutorial como referencia. Esta entrada será larga, pero espero que pueda ser útil. Vamos allá...

1. Introducción

Este tutorial contiene los pasos a seguir para la generación de clases cliente de servicios web (WS), a partir de la definición del WS mediante su fichero de definición de servicios (WSDL), utilizando Apache CXF.

Partimos de un fichero WSDL, generado por una entidad externa. Este fichero contiene toda la información necesaria sobre los servicios a los que vamos a conectar. Utilizando las librerías de Apache CXF, se generarán clases cliente Java a partir del WSDL, de forma que se pueda realizar la invocación a los diferentes servicios descritos en el WSDL de forma sencilla.

2. Instalación del entorno

Para la utilización de Apache CXF, es necesario instalar en Eclipse varios plugins, y descargarse la librería, siguiendo los pasos descritos a continuación.

  1. Instalar en Eclipse soporte para CXF
  2. Descargar la librería de CXF 2.3.11
  3. Configurar las preferencias de CXF
  4. Definir un server para Tomcat 6

2.1 Instalar en Eclipse soporte para CXF

En primer lugar, hay que comprobar que Eclipse tiene instalado soporte para CXF. En el menú Window -> Preferences -> Web Services tiene que haber una entrada para ‘CXF 2.x Preferences’.

Si no existe la entrada, es necesario instalar en Eclipse el soporte para ‘CXF Tooling’. En el menú Help -> Install new software.

Seleccionar el repositorio ‘The Eclipse Web Tools Platform (WTP) Project update site – http://download.eclipse.org/webtools/updates’, y filtrar por ‘cxf’. De los paquetes que muestre, marcar para instalar ‘CXF Web Services’. Según la versión de Eclipse pueden variar los números de versión de WTP y CXF Web Services.

2.2 Descargar la librería de CXF

Adicionalmente a añadir el soporte en Eclipse, es necesario descargar una versión de la librería CXF, y configurar Eclipse para indicarle donde está situada.

En primer lugar, descargar la versión 2.3.11 de Apache CXF desde http://archive.apache.org/dist/cxf/. La versión 2.3.11 es la necesaria para CXF Web Services 0.4.2, es posible que versiones posteriores de CXF Web Services necesiten versiones más actualizadas de Apache CXF.

Una vez descargado, descompimir el fichero apache-cxf-2.3.11.tar.gz en un directorio.

2.3 Configurar las preferencias de CXF

El directorio donde se ha descomprimido el tar.gz anterior hay que configurarlo en las preferencias de CXF Web Services:

2.4 Definir un server para Tomcat 6

Es necesario tener definido un server para Tomcat 6. Si no hay ninguno definido en Eclipse, puede crearse uno mediante los siguientes pasos.

En el menú Windows -> Preferences -> Server -> Runtime Environments, se muestra un listado de los servers definidos dentro de Eclipse.

Si en el listado no aparece ningún server de tipo ‘Apache Tomcat v6.0’, pulsar en el botón ‘Add...’.

En el diálogo de selección de tipo, elegir ‘Apache Tomcat v6.0’, y pulsar en el botón ‘Next >’.

En el diálogo Tomcat Server, definir un nombre significativo (en este caso, ‘Apache Tomcat v6.0’), y el directorio donde está descomprimida la versión 6.0.14 de Tomcat. A continuación pulsar en el botón ‘Finish’, y quedará definido el server.

(Ir arriba)

3. Generación de clases cliente

A partir del fichero de definición del servicio web WSDL, utilizando Apache CXF se generan las clases Java cliente para invocar al servicio, sin necesidad de tratar directamente con el protocolo SOAP subyacente ni con ficheros XML.

Partimos de un proyecto de ejemplo ‘Test’, y de un fichero de ejemplo sampleservice.wsdl.

Pulsando con el botón derecho sobre el fichero sampleservice.wsdl, seleccionar la opción Web Services -> Generate Client:

Nos mostrará el diálogo inicial de generación, sobre el que habrá que realizar algunos ajustes.

En primer lugar, hay que generar sólo clases para el cliente, por lo que moveremos la barra izquierda hacia abajo hasta que aparezca ‘Develop client’ en la imagen central:

En segundo lugar, hay que cambiar la librería de servicios web utilizados: por defecto selecciona Apache Axis, y hay que cambiarla por CXF. Pulsando sobre el enlace ‘Web service runtime: Apache Axis’ muestra el siguiente diálogo:

Aquí se selecciona en el cuadro ‘Web Service runtime’ la opción ‘Apache CXF 2.x’, y en el cuadro ‘Server’, la opción ‘Tomcat v6.0 Server at localhost’. Tras pulsar en el botón ‘OK’, vuelve al diálogo inicial:

Puede mostrar un mensaje de error indicando que el tipo de proyecto no está soportado. Hay que pulsar en el enlace ‘Client project: Test’, con lo que mostrará el siguiente diálogo:

Aquí se da un nuevo nombre al proyecto donde creará las clases cliente, y se selecciona el tipo de proyecto ‘CXF Web Services Project’. Es mejor generar las clases en un proyecto nuevo (Test2), y luego copiar dichas clases al proyecto donde vayan a ser utilizadas.

De vuelta al diálogo incial, con todos los cambios de configuración realizados, pulsamos en el botón ‘Next >’.

En este diálogo se configura el nombre del paquete en el que queremos que se generen las clases (en este caso se ha puesto com.isoco.test2.ws). Nótese que el directorio de salida de las clases será dentro del proyecto ‘Test2’.

Pulsando en el botón ‘Next >’, se mostrará el diálogo de selección de opciones de generación.

En este diálogo hay que dejar las opciones por defecto. Finalmente, pulsando en el botón ‘Finish’, se generarán las clases cliente correspondientes al WSDL inicialmente seleccionado.

(Ir arriba)

4. Servicios web securizados

Para la invocación de servicios web seguros, son necesarios pasos adicionales en la preparación y configuración del cliente de los servicios web.

Apache CXF dispone de una implementación del estándar WS-Security (implementado principalmente en WSS4J) para proporcionar un nivel de seguridad adicional al simple uso de HTTPS.

Existen dos técnicas principales para la securización de servicios web con Apache CXF utilizando certificados X509:

  1. Utilizar WS-SecurityPolicy. El WSDL de definición de los servicios web incluye la definición de las medidas de seguridad a tomar, con la inclusión de tags <wsp:policy>.

  2. Utilizar interceptores. Este es el mecanismo original proporcionado por Apache CXF, y es el que se utilizará en este documento.

4.1. Preparación de certificados

La comunicación entre el cliente de los servicios web y el servidor donde residen los servicios web se realizará en última instancia enviando ficheros SOAP (XML) con firma digital, y para la generación de la firma digital son necesarios claves públicas/privadas, contenidas en certificados X509.

Se puede generar un certificado para desarrollo con las siguientes instrucciones:

  1. Generar certificado cliente. Para producción el certificado debería ser generado por una entidad certificadora reconocida.
    keytool -genkey -keyalg RSA -sigalg SHA1withRSA -validity 365 
            -alias testkey -keypass testpwd -storepass testpwd
            -keystore testKeystore.jks -dname "cn=testclient,o=isoco"
    
  2. Listar el contenido del almacén de claves (keystore)
    keytool -list -storepass testpwd -keystore testKeystore.jks
  3. Listar el contenido de un certificado contenido en el keystore
    keytool -list -v -alias testkey -storepass testpwd 
            -keystore testKeystore.jks
  4. Exportar la clave pública del certificado del cliente. Este fichero testClient.cer con la clave pública debe ser importado en el keystore del servidor, para que el servidor pueda decodificar las firmas realizadas con el certificado del cliente.
    keytool -export -rfc -keystore testKeystore.jks 
            -storepass testpwd -alias testkey -file testClient.cer
  5. Importar la clave pública del certificado del servidor. Este fichero server.cer debe ser enviado por los responsables del servidor, y permitirá al cliente validar la firma de los mensajes enviados por el servidor.
    keytool -import -trustcacerts -keystore testKeystore.jks 
            -storepass testpwd -alias testkey -file server.cer –noprompt

Al finalizar el proceso, dispondremos de un keystore (testKeystore.jks) que contendrá el certificado con la clave pública/privada del cliente, y un certificado con la clave pública del servidor.

Otra alternativa a intercambiar entre cliente y servidor certificados, es que ambos utilicen certificados emitidos por autoridades de certificación (CAs) reconocidas por ambos, es decir, que los certificados de las CAs o bien estén en el almacén de certificados estándar de la máquina virtual Java (cacerts), o bien se añadan al testKeystore.jks.

4.2. Fichero de configuración

Todos los datos referentes al keystore a utilizar, alias de la clave, etc. se definen en un fichero de propiedades, que en nuestro caso hemos llamado testKeystore.properties. Su contenido es el siguiente:

org.apache.ws.security.crypto.merlin.keystore.type=jks
org.apache.ws.security.crypto.merlin.keystore.password=testpwd
org.apache.ws.security.crypto.merlin.keystore.alias=testkey
org.apache.ws.security.crypto.merlin.file=testKeystore.jks

Las cuatro propiedades definen:

  • El tipo de keystore: jks = Java Key Store.
  • El password de acceso al keystore.
  • El alias de la clave privada del cliente almacenada en el keystore.
  • El nombre del fichero que contiene el keystore.

(Ir arriba)

5. Incorporación a un proyecto existente

Para la incorporación a un proyecto existente de las clases generadas por Apache CXF, son necesarios tres pasos:

  • Copiar clases generadas.
  • Copiar librerías de soporte.
  • Copiar ficheros de configuración.

5.1. Copiar clases generadas

Apache CXF genera las siguientes clases:

  • una clase principal con la definición del servicio: en este ejemplo, PersonasService;
  • una clase por cada una de las operaciones que ofrezca el servicio: en este ejemplo, AddPersona, GetPersona, GetPersonas, RemovePersona;
  • una clase para contener la petición y la respuesta de cada operación: en este ejemplo, AddPersonaResponse, GetPersonaResponse, GetPersonasResponse y RemovePersonaResponse;
  • una clase para cada objeto de datos que se pase como parámetro o se reciba como respuesta en una operación: en este ejemplo, Persona;
  • una clase para cada excepción que esté definida en el servicio: en este ejemplo ninguna, son clases que acaban en XXXFault.
  • dos clases de infraestructura de bajo nivel: ObjectFactory y package-info;
  • varias clases de ejemplo de uso, que pueden borrarse: PersonasServiceImpl, PersonasServiceService y PersonasService_PersonasServicePort_Client.

Todas las clases generadas deben copiarse al proyecto destino, manteniento la estructura de paquetes, excepto las clases de ejemplo de uso.

5.2. Copiar librerías de soporte

Para incluir el uso de Apache CXF dentro de un proyecto, es necesario añadir las librerías de soporte que lo forman. Inicialmente, estas son las librerías necesarias básicas:

Estas librerías están situadas en el directorio lib/ de la instalación del Apache CXF. Este directorio contiene un fichero WHICH_JARS con información de las librerías necesarias según la funcionalidad que se quiera utilizar de CXF.

Para utilizar la funcionalidad de firma y encriptación, es necesario incluir las siguientes librerías:

La librería bcprov-jdk15-1.45.jar sólo es necesaria si se va a utilizar algún algoritmo de firma o encriptación que no está incluido dentro del JDK.

5.3. Copiar archivos de configuración

Si se va a utilizar la funcionalidad de firma y/o encriptación, es necesario copiar el keystore y el fichero de propiedades (testKeystore.jks y testKeystore.properties) al classpath del proyecto, para que se incluyan en algún jar. Si es un proyecto maven, deben copiarse dentro del subdirectorio src/main/resources de algún módulo.

Como alternativa, estos ficheros pueden colocarse en algún directorio del servidor de aplicaciones que forme parte del classpath, como por ejemplo el subdirectorio server/default/conf/ de JBoss. Esta aproximación tiene la ventaja de que no se incluye dentro del distribuible de la aplicación ni el keystore ni el password de acceso a este.

5.4. Uso con maven

Para utilizar CXF en un proyecto gestionado con maven, es necesario incluir las siguientes dependencias en el pom.xml:

  <dependencies>
    <dependency>
        <groupId>org.apache.cxf</groupId>
        <artifactId>cxf-rt-frontend-jaxws</artifactId>
        <version>2.3.11</version>
    </dependency>
    <dependency>
        <groupId>org.apache.cxf</groupId>
        <artifactId>cxf-rt-transports-http</artifactId>
        <version>2.3.11</version>
    </dependency>
  </dependencies>

Para utilizar la funcionalidad de firma y encriptación, es necesario incluir la siguiente depencencia:

  <dependency>
    <groupId>org.apache.cxf</groupId>
    <artifactId>cxf-rt-ws-security</artifactId>
    <version>2.3.11</version>
  </dependency>

Adicionalmente, pueden ser necesarias otras dependencias, según la funcionalidad que se utilice de CXF. Puede encontrarse más información en http://cxf.apache.org/docs/using-cxf-with-maven.html.

(Ir arriba)

6. Invocación al WS

6.1. Invocación normal

A partir de las clases generadas, la invocación al servicio web es muy simple, basándose en el siguiente ejemplo (modificando los valores en cursiva):

    JaxWsProxyFactoryBean factory = new JaxWsProxyFactoryBean();
    factory.setServiceClass(PersonasService.class);
    factory.setAddress(“http://xxxx/services/PersonasServicePort?wsdl”);
    PersonasService service = (PersonasService) factory.create();

A partir de aquí, con la variable service, se pueden realizar invocaciones a las operaciones ofrecidas por el servicio web.

6.2 Invocación a WS seguros

Cuando se va a invocar a WS seguros, es necesario configurar las propiedades de los interceptores, que serán los encargados de realizar la firma y validación de forma transparente de los mensajes SOAP que se envíen / reciban del servidor.

El siguiente ejemplo muestra como configurar un interceptor de salida para que realice firma y timestamp, y uno de entrada para que valide la firma y timestamp enviados por el servidor:

    Map props = new HashMap();
    props.put("action","Timestamp Signature");
    props.put("user","testkey");
    props.put("signaturePropFile","testKeystore.properties");
    props.put("signatureKeyIdentifier","SKIKeyIdentifier");
    props.put("passwordCallbackClass",
        "com.isoco.test2.ws.KeystorePwdCallback");
    props.put("signatureParts",
        "{Element}{http://schemas.xmlsoap.org/soap/envelope/}Body");
    props.put("signatureAlgorithm",
        "http://www.w3.org/2000/09/xmldsig#rsa-sha1");
    
    WSS4JOutInterceptor outInterceptor = new WSS4JOutInterceptor(props);
    WSS4JInInterceptor inInterceptor = new WSS4JInInterceptor(props);
        
    factory.getOutInterceptors().add(outInterceptor);
    factory.getInInterceptors().add(inInterceptor);

A los objetos WSS4JOutInterceptor y WSS4JInInterceptor se les pasa en su constructor un mapa de parámetros de configuración:

  • action: acciones a realizar, separadas por espacios.
  • user: usuario que realizará la acción, debe coincider con el alias de la clave a utilizar para realizar la firma.
  • signaturePropFile: fichero que contiene las propiedades del keystore a utilizar (descrito en el punto 4.2).
  • signatureKeyIdentifier: forma de identificar la clave utilizada para la firma. El valor SKIKeyIdentifier indica que la petición incluirá el identificador de la clave codificado en Base64 (más info sobre los valores posibles de este parámetro en http://coheigea.blogspot.com.es/2013/03/signature-and-encryption-key.html).
  • passwordCallbackClass: clase que implementa el interfaz CallbackHandler y devuelve el password del usuario.
  • signatureParts: partes del mensaje SOAP que se van a firmar (el Body del mensaje). Si se quiere firmar también el Timestamp el valor de la propiedad debe ser
        props.put("signatureParts",
            "{Element}{http://docs.oasis-open.org/wss/2004/01/oasis-200401-
            wss-wssecurity-utility-1.0.xsd}Timestamp;{Element}
            {http://schemas.xmlsoap.org/soap/envelope/}Body");
    
  • signatureAlgorith: algoritmo de firma, RSA-SH1.

Para validar los mensajes generados y recibidos, se puede añadir un interceptor que muestre por el log dichos mensajes:

        LoggingOutInterceptor logOutInterceptor = new LoggingOutInterceptor();
        logOutInterceptor.setPrettyLogging(true);
        
        LoggingInInterceptor logInInterceptor = new LoggingInInterceptor();
        logInInterceptor.setPrettyLogging(true);
        
        factory.getOutInterceptors().add(loggingOutInterceptor);
        factory.getInInterceptors().add(loggingInInterceptor);

Si no se muestran los mensajes de funcionamiento de CXF, hay que asegurarse de que en el fichero de configuración de log4j (log4j.properties) está habilitado el log para las clases org.apache.cxf:

        log4j.logger.org.apache.cxf=INFO

(Ir arriba)

7. Referencias

(Ir arriba)

17 noviembre 2013

Soluciones de baja tecnología

Hoy he pasado por la maratón de Valencia y en el kilómetro 41 me he encontrado con una especie de meta volante, y un locutor que por megafonía iba animando a los corredores que pasaban.

Me ha llamado la atención que iba animando a cada corredor que pasaba llamándolo por su nombre. Después de un par de minutos nombrando corredores a una velocidad de uno por segundo, mi deformación profesional de informático me ha hecho preguntarme como lo podía estar haciendo:

  • Una cámara reconociendo los números de los dorsales? Seguro que no podía acertar tantos...
  • Dorsales con un chip RFID? Eso los detectaría todos...

Al final, la solución era de mucha más BAJA TECNOLOGÍA, los dorsales, además del número, llevaban el nombre del corredor: efectivo, simple de implementar y sencillo.

Esto me ha recordado que muchas veces hay que resistir la tentación de buscar soluciones arquitectónicas innecesariamentre complicadas: dorsales con nombre, dorsales con nombre, dorsales con nombre, ...