Páginas

jueves, 8 de noviembre de 2012

Configurar Log4j en Apache CXF

En la anterior entrada dejamos montado un ejemplo con Apache CFX y WS-Security. Pero como Apache CXF utiliza por defecto slf4j el lugar de log4j, teníamos el log de las peticiones y respuestas en ficheros distintos al resto de los módulos, donde usamos log4j.

Vamos a repasar como ajustar ambos módulos para que se genere la traza en nuestro fichero de log habitual.

Aplicativo cxf-web

1) Deberemos ajustar el fichero /src/main/resources/log4j.properties con el siguiente contenido:

log4j.rootCategory=INFO,CONSOLA

log4j.logger.es.ine.cxf.ws=INFO

log4j.logger.org.apache.cxf.interceptor.LoggingInInterceptor.level=INFO
log4j.logger.org.apache.cxf.interceptor.LoggingOutInterceptor.level=INFO
log4j.logger.org.apache.cxf.ws.rm.security.wss4j.WSS4JInInterceptor.level=INFO
log4j.logger.org.apache.cxf.ws.rm.security.wss4j.WSS4JOutInterceptor.level=INFO

log4j.appender.CONSOLA=org.apache.log4j.ConsoleAppender
log4j.appender.CONSOLA.layout=org.apache.log4j.PatternLayout
log4j.appender.CONSOLA.layout.ConversionPattern=%d %p [%c] - <%m>%n

log4j.appender.FICHERO=org.apache.log4j.RollingFileAppender
log4j.appender.FICHERO.file=${log4j.debug.ruta_fichero}/cxf-web.log
log4j.appender.FICHERO.append=true
log4j.appender.FICHERO.MaxFileSize=${log4j.debug.MaxFileSize}
log4j.appender.FICHERO.MaxBackupIndex=${log4j.debug.MaxBackupIndex}
log4j.appender.FICHERO.layout=org.apache.log4j.PatternLayout
log4j.appender.FICHERO.layout.ConversionPattern=[%d{dd-MM-yyyy HH\:mm\:ss,SSS}] thread\:[%t] nivelTraza\:[%p] lugarTraza\:[%c] mensajeTraza[%m]%n


2) Deberemos crear un fichero llamado:

/src/main/resources/META-INF/cxf/org.apache.cxf.Logger

El contenido de este fichero será:

org.apache.cxf.common.logging.Log4jLogger

3) Deberemos ajustar nuestra clase cliente así:

package es.ins.sgtic.app;

import java.net.URL;
import java.util.HashMap;
import java.util.Map;

import javax.xml.namespace.QName;

import org.apache.cxf.binding.soap.saaj.SAAJInInterceptor;
import org.apache.cxf.binding.soap.saaj.SAAJOutInterceptor;
import org.apache.cxf.endpoint.Client;
import org.apache.cxf.endpoint.Endpoint;
import org.apache.cxf.frontend.ClientProxy;
import org.apache.cxf.interceptor.LoggingInInterceptor;
import org.apache.cxf.interceptor.LoggingOutInterceptor;
import org.apache.cxf.ws.security.wss4j.WSS4JInInterceptor;
import org.apache.cxf.ws.security.wss4j.WSS4JOutInterceptor;

import es.ine.cxf.ws.impl.Calculadora;
import es.ine.cxf.ws.impl.CalculadoraImplService;

public class AppTest {

    private static final QName SERVICE_NAME = new QName("http://impl.ws.cxf.ine.es/", "CalculadoraImplService");
   
    public static void main(String[] args) {

        //Definimos el servicio web a usar
        final URL wsdlURL = CalculadoraImplService.WSDL_LOCATION;
        final CalculadoraImplService calcService = new CalculadoraImplService(wsdlURL, SERVICE_NAME);
        final Calculadora calculadora = calcService.getCalculadoraImplPort();
               
        //Obtenemos la referencia al endpoint del ws
        final Client client = ClientProxy.getClient(calculadora);
        final Endpoint cxfEndpoint = client.getEndpoint();
       
        //Ajustamos el log del cliente
        final LoggingInInterceptor incerceptorLogIn = new LoggingInInterceptor();
        incerceptorLogIn.setPrettyLogging(true);
        final LoggingOutInterceptor incerceptorLogOut = new LoggingOutInterceptor();
        incerceptorLogOut.setPrettyLogging(true);
        client.getInInterceptors().add(incerceptorLogIn);
        client.getOutInterceptors().add(incerceptorLogOut);

       
        //Configuramos el interceptor de cifrado/firmado para los envíos
        final Map<String,Object> outProps = new HashMap<String,Object>();
        outProps.put("action", "Timestamp Signature Encrypt");
        outProps.put("user", "edugarci");
        outProps.put("signaturePropFile", "props/client-key-store.properties");
        outProps.put("signatureKeyIdentifier", "DirectReference");
        outProps.put("passwordCallbackClass", "es.ine.cxf.ws.sec.PasswordCallback");
        outProps.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");
        outProps.put("encryptionPropFile", "props/client-trust-store.properties");
        outProps.put("encryptionUser", "desarrollo-internet");
        outProps.put("encryptionParts", "{Element}{http://www.w3.org/2000/09/xmldsig#}Signature;{Content}{http://schemas.xmlsoap.org/soap/envelope/}Body");
        outProps.put("encryptionSymAlgorithm", "http://www.w3.org/2001/04/xmlenc#tripledes-cbc");
        outProps.put("encryptionKeyTransportAlgorithm", "http://www.w3.org/2001/04/xmlenc#rsa-1_5");
        WSS4JOutInterceptor wssOut = new WSS4JOutInterceptor(outProps); //request
        cxfEndpoint.getOutInterceptors().add(wssOut);
        cxfEndpoint.getOutInterceptors().add(new SAAJOutInterceptor());
       
        //Configuramos el interceptor de descifrado/verificacion-firma para las recepciones
        Map<String,Object> inProps= new HashMap<String,Object>();
        inProps.put("action", "Timestamp Signature Encrypt");
        inProps.put("signaturePropFile", "props/client-trust-store.properties");
        inProps.put("passwordCallbackClass", "es.ine.cxf.ws.sec.PasswordCallback");
        inProps.put("decryptionPropFile", "props/client-key-store.properties");
        WSS4JInInterceptor wssIn = new WSS4JInInterceptor(inProps); //response
        cxfEndpoint.getInInterceptors().add(wssIn);
        cxfEndpoint.getInInterceptors().add(new SAAJInInterceptor());
       
        //Lanzamos unas peticiones de ejemplo
        System.out.println(String.format("port.suma(15, 3) --> %d",calculadora.suma(15, 3)));
        System.out.println(String.format("port.resta(15, 3) --> %d",calculadora.resta(15, 3)));
        System.out.println(String.format("port.multiplica(15, 3) --> %d",calculadora.multiplica(15, 3)));
        System.out.println(String.format("port.divide(15, 3) --> %d",calculadora.divide(15, 3)));
    }
}


Aplicativocxf-client

1) Deberemos ajustar el fichero /src/main/resources/log4j.properties con el siguiente contenido:

log4j.rootCategory=INFO,CONSOLA

log4j.logger.es.ine.cxf.ws=INFO

log4j.logger.org.apache.cxf.interceptor.LoggingInInterceptor.level=INFO
log4j.logger.org.apache.cxf.interceptor.LoggingOutInterceptor.level=INFO
log4j.logger.org.apache.cxf.ws.rm.security.wss4j.WSS4JInInterceptor.level=INFO
log4j.logger.org.apache.cxf.ws.rm.security.wss4j.WSS4JOutInterceptor.level=INFO

log4j.appender.CONSOLA=org.apache.log4j.ConsoleAppender
log4j.appender.CONSOLA.layout=org.apache.log4j.PatternLayout
log4j.appender.CONSOLA.layout.ConversionPattern=%d %p [%c] - <%m>%n

log4j.appender.FICHERO=org.apache.log4j.RollingFileAppender
log4j.appender.FICHERO.file=${log4j.debug.ruta_fichero}/cxf-web.log
log4j.appender.FICHERO.append=true
log4j.appender.FICHERO.MaxFileSize=${log4j.debug.MaxFileSize}
log4j.appender.FICHERO.MaxBackupIndex=${log4j.debug.MaxBackupIndex}
log4j.appender.FICHERO.layout=org.apache.log4j.PatternLayout
log4j.appender.FICHERO.layout.ConversionPattern=[%d{dd-MM-yyyy HH\:mm\:ss,SSS}] thread\:[%t] nivelTraza\:[%p] lugarTraza\:[%c] mensajeTraza[%m]%n


2) Deberemos crear un fichero llamado:

/src/main/resources/META-INF/cxf/org.apache.cxf.Logger

El contenido de este fichero será:

org.apache.cxf.common.logging.Log4jLogger

3) Deberemos ajustar el contenido del fichero applicationContext.xml, dejándolo así:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:jaxws="http://cxf.apache.org/jaxws"
    xmlns:cxf="http://cxf.apache.org/core"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
                        http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
                        http://cxf.apache.org/jaxws
                        http://cxf.apache.org/schemas/jaxws.xsd
                        http://cxf.apache.org/core
                        http://cxf.apache.org/schemas/core.xsd">

    <import resource="classpath:META-INF/cxf/cxf.xml" />
    <import resource="classpath:META-INF/cxf/cxf-extension-soap.xml" />
    <import resource="classpath:META-INF/cxf/cxf-servlet.xml" />

    <bean id="firmaPeticion" class="org.apache.cxf.ws.security.wss4j.WSS4JInInterceptor">
        <constructor-arg>
            <map>
                <entry key="action" value="Timestamp Signature Encrypt" />
                <!-- Ajustamos la verificación de la firma de las peticiones que nos
                    llegan -->
                <entry key="signaturePropFile" value="props/server-trust-store.properties" />
                <!-- Ajustamos el desencriptado de los datos de la petición que recibimos -->
                <entry key="decryptionPropFile" value="props/server-key-store.properties" />
                <entry key="passwordCallbackClass" value="es.ine.cxf.ws.sec.PasswordCallback" />
            </map>
        </constructor-arg>
    </bean>

    <bean id="firmaRespuesta" class="org.apache.cxf.ws.security.wss4j.WSS4JOutInterceptor">
        <constructor-arg>
            <map>
                <entry key="action" value="Timestamp Signature Encrypt" />
                <!-- Ajustamos el firmado de las respuestas -->
                <entry key="signaturePropFile" value="props/server-key-store.properties" />
                <entry key="user" value="desarrollo-internet" />
                <!-- Ajustamos el encriptado de las respuestas -->
                <entry key="encryptionPropFile" value="props/server-trust-store.properties" />
                <entry key="encryptionUser" value="edugarci" />
                <entry key="signatureKeyIdentifier" value="DirectReference" />
                <entry key="passwordCallbackClass" value="es.ine.cxf.ws.sec.PasswordCallback" />
                <entry key="signatureParts"
                    value="{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" />
                <entry key="encryptionParts"
                    value="{Element}{http://www.w3.org/2000/09/xmldsig#}Signature;{Content}{http://schemas.xmlsoap.org/soap/envelope/}Body" />
                <entry key="encryptionKeyTransportAlgorithm" value="http://www.w3.org/2001/04/xmlenc#rsa-1_5" />
                <entry key="encryptionSymAlgorithm" value="http://www.w3.org/2001/04/xmlenc#tripledes-cbc" />
            </map>
        </constructor-arg>
    </bean>

    <jaxws:endpoint xmlns:tns="http://impl.ws.cxf.ine.es/"
        id="calculadora" implementor="es.ine.cxf.ws.impl.CalculadoraImpl"
        wsdlLocation="wsdl/calculadoraimpl.wsdl" endpointName="tns:CalculadoraImplPort"
        serviceName="tns:CalculadoraImplService" address="/CalculadoraImplPort">
        <jaxws:outInterceptors>
            <bean class="org.apache.cxf.binding.soap.saaj.SAAJOutInterceptor" />
            <ref bean="firmaRespuesta" />
        </jaxws:outInterceptors>
        <jaxws:inInterceptors>
            <ref bean="firmaPeticion" />
            <bean class="org.apache.cxf.binding.soap.saaj.SAAJInInterceptor" />
        </jaxws:inInterceptors>
    </jaxws:endpoint>

    <bean id="abstractLoggingInterceptor" abstract="true">
        <property name="prettyLogging" value="true" />
    </bean>
    <bean id="loggingInInterceptor" class="org.apache.cxf.interceptor.LoggingInInterceptor"
        parent="abstractLoggingInterceptor" />
    <bean id="loggingOutInterceptor" class="org.apache.cxf.interceptor.LoggingOutInterceptor"
        parent="abstractLoggingInterceptor" />

    <cxf:bus>
        <cxf:inInterceptors>
            <ref bean="loggingInInterceptor" />
        </cxf:inInterceptors>
        <cxf:outInterceptors>
            <ref bean="loggingOutInterceptor" />
        </cxf:outInterceptors>
        <cxf:outFaultInterceptors>
            <ref bean="loggingOutInterceptor" />
        </cxf:outFaultInterceptors>
        <cxf:inFaultInterceptors>
            <ref bean="loggingInInterceptor" />
        </cxf:inFaultInterceptors>
    </cxf:bus>

</beans>
 

Apache CXF + WS-Security + Tomcat 6

Finalidad

Se ha montado una prueba de cifrado y firmado de comunicaciones de servicios web con Apache CXF. El objetivo es montar un servicio web y un cliente donde  se cifren y firmen tantos las peticiones de los clientes como las respuestas del servicio web.

Entorno

- Eclipse Helios
- Apache Tomcat 6.0.35
- Apache CXF 2.4.10
- Maven2
- JDK 1.6.0_31

Preparación del entorno

Como ya teníamos instalado Eclipse Helios, el plugin para Maven y el Tomcat 6.0.35, empezaremos el tutorial con la descarga e instalación de Apache CXF.

1) Descargamos Ápache CXF de la URL:

http://www.apache.org/dyn/closer.cgi?path=/cxf/2.4.10/apache-cxf-2.4.10.tar.gz

2) Lo descomprimimos en algún directorio de nuestro disco. Por aquello de tener las cosas organizadas y prepararnos por si tuviésemos que manejar otras versiones de apache-cxf en el futuro, es recomendable ubicarlo en:

/home/xxxxx/apache-cxf/apache-cxf-2.4.10

3) Abrimos Eclipse Helios y definimos un nuevo workspace, por ejemplo:

/home/xxxxx/wks-helios/WksTestApacheCXFJetty

4) Definimos el servidor Tomcat 6.0.35.

- Hacemos clic en la opción de menú: Window/Show View/Other/Servers
- Sobre la vista Servers sacamos el menú contextual pulsado el botón derecho y seleccionamos la opción New/Server eligiendo el Tomcat 6 y definiendo la ruta de instalación del mismo en el sistema (donde se descomprimió).

5) Definimos la ubicación de las librerías de Apache CXF 2.4.10.


- Hacemos clic en la opción de menú: Window/Preferences/Web Services/CXF 2.X Preferences
- Pulsamos el botón Add e indicamos el path de descompresión de Apache CXF 2.4.10

Proyecto padre

Dado que estamos con maven, montamos un proyecto multimodular "cxf" con dos módulos. El primer módulo "cxf-web" alojará los servicios web. El segundo módulo "cxf-client" alojará un aplicativo java stand-alone que ejercerá de cliente.

El fichero pom.xml de este proyecto padre sería:

<project xmlns="http://maven.apache.org/POM/4.0.0" 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 

    http://maven.apache.org/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>es.org.sgtic</groupId>
    <artifactId>cxf</artifactId>
    <version>1.0</version>
    <packaging>pom</packaging>
    <build>
        <resources>
            <resource>
                <filtering>false</filtering>
                <directory>src/main/resources</directory>
                <excludes>
                    <exclude>*.properties</exclude>
                    <exclude>applicationContext*.xml</exclude>
                </excludes>
            </resource>
            <resource>
                <filtering>true</filtering>
                <directory>src/main/resources</directory>
                <includes>
                    <include>*.properties</include>
                    <include>applicationContext*.xml</include>
                </includes>
            </resource>
        </resources>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>${maven-compiler-plugin.source}</source>
                    <target>${maven-compiler-plugin.target}</target>
                    <encoding>${maven-compiler-plugin.encoding}</encoding>
                </configuration>
            </plugin>
        </plugins>
    </build>
    <properties>
        <!-- Propiedades plugin: maven-compiler-plugin -->
        <maven-compiler-plugin.source>1.6</maven-compiler-plugin.source>
        <maven-compiler-plugin.target>1.6</maven-compiler-plugin.target>
        <maven-compiler-plugin.encoding>UTF-8</maven-compiler-plugin.encoding>
        <!-- Propiedades plugin: tomcat-maven-plugin -->
        <tomcat_maven_plugin.deploy_user>admin</tomcat_maven_plugin.deploy_user>
        <tomcat_maven_plugin.deploy_password>admin</tomcat_maven_plugin.deploy_password>
        <tomcat_maven_plugin.sourceEncoding>UTF-8</tomcat_maven_plugin.sourceEncoding>
        <tomcat_maven_plugin.server_port>localhost:8080</tomcat_maven_plugin.server_port>
        <!-- Propiedades del modulo cxf-web -->
        <proyecto.web.final_name>cxf-web</proyecto.web.final_name>
        <!-- Propiedades del modulo cxf-client -->
        <proyecto.cliente.final_name>cxf-client</proyecto.cliente.final_name>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    </properties>
    <modules>
        <module>cxf-web</module>
        <module>cxf-client</module>
    </modules>
</project>


Módulo cxf-web

1) El fichero pom.xml de este módulo sería:

<?xml version="1.0"?>
<project
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 

    http://maven.apache.org/xsd/maven-4.0.0.xsd"
    xmlns="http://maven.apache.org/POM/4.0.0" 

    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <artifactId>cxf</artifactId>
        <groupId>es.org.sgtic</groupId>
        <version>1.0</version>
    </parent>
    <groupId>es.org.sgtic</groupId>
    <artifactId>cxf-web</artifactId>
    <version>1.0</version>
    <packaging>war</packaging>
    <dependencies>
        <dependency>
            <groupId>es.org.sgtic.lib.spring.2.5.0.j2ee</groupId>
            <artifactId>el-api</artifactId>
            <version>1.5.0_06</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>es.org.sgtic.lib.tomcat6</groupId>
            <artifactId>servlet-api</artifactId>
            <version>2.5</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.apache.cxf</groupId>
            <artifactId>cxf-rt-frontend-jaxws</artifactId>
            <version>2.6.3</version>
        </dependency>
        <dependency>
            <groupId>org.apache.cxf</groupId>
            <artifactId>cxf-rt-transports-http</artifactId>
            <version>2.6.3</version>
        </dependency>
        <dependency>
            <groupId>org.apache.cxf</groupId>
            <artifactId>cxf-rt-transports-http-jetty</artifactId>
            <version>2.6.3</version>
        </dependency>
        <dependency>
            <groupId>org.apache.cxf</groupId>
            <artifactId>cxf-rt-ws-rm</artifactId>
            <version>2.6.3</version>
        </dependency>
        <dependency>
            <groupId>org.apache.cxf</groupId>
            <artifactId>cxf-rt-ws-security</artifactId>
            <version>2.6.3</version>
        </dependency>
        <dependency>
            <groupId>org.apache.cxf</groupId>
            <artifactId>cxf-rt-ws-addr</artifactId>
            <version>2.6.3</version>
        </dependency>
        <dependency>
            <groupId>org.apache.cxf</groupId>
            <artifactId>cxf-rt-ws-policy</artifactId>
            <version>2.6.3</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring</artifactId>
            <version>2.5.6</version>
        </dependency>
    </dependencies>
    <build>
        <resources>
            <resource>
                <filtering>false</filtering>
                <directory>src/main/resources</directory>
                <excludes>
                    <exclude>*.properties</exclude>
                    <exclude>applicationContext*.xml</exclude>
                </excludes>
            </resource>
            <resource>
                <filtering>true</filtering>
                <directory>src/main/resources</directory>
                <includes>
                    <include>*.properties</include>
                    <include>applicationContext*.xml</include>
                </includes>
            </resource>
        </resources>
        <finalName>${proyecto.web.final_name}</finalName>
        <plugins>
            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>tomcat-maven-plugin</artifactId>
                <version>2.1-alpha-1</version>
                <configuration>
                    <warFile>${project.build.directory}/${project.build.finalName}.war</warFile>
                    <update>true</update>
                    <path>/${proyecto.web.final_name}</path>
                    <charset>${tomcat_maven_plugin.sourceEncoding}</charset>
                    <url>http://${tomcat_maven_plugin.server_port}/manager</url>
                    <username>${tomcat_maven_plugin.deploy_user}</username>
                    <password>${tomcat_maven_plugin.deploy_password}</password>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>


2) La interfaz de nuestro servicio web, en este caso una calculadora muy simple, sería:

package es.org.cxf.ws.impl;

import javax.jws.WebMethod;
import javax.jws.WebService;

@WebService(name = "Calculadora", targetNamespace = "http://impl.ws.cxf.org.es/")
public interface Calculadora {

    @WebMethod(operationName = "suma", action = "urn:Suma")
    public int suma(int x, int y);

    @WebMethod(operationName = "resta", action = "urn:Resta")
    public int resta(int x, int y);

    @WebMethod(operationName = "multiplica", action = "urn:Multiplica")
    public int multiplica(int x, int y);

    @WebMethod(operationName = "divide", action = "urn:Divide")
    public int divide(int x, int y);

}


3) La implementación de la calculadora sería:

package es.org.cxf.ws.impl;

import javax.jws.WebService;

@WebService(targetNamespace = "http://impl.ws.cxf.org.es/", endpointInterface = "es.org.cxf.ws.impl.Calculadora", portName = "CalculadoraImplPort", serviceName = "CalculadoraImplService")
public class CalculadoraImpl implements Calculadora {
   
    public int suma(int x, int y){
        return x+y;
    }
    public int resta(int x, int y){
        return x-y;
    }
    public int multiplica(int x, int y){
        return x*y;
    }
    public int divide(int x, int y){
        return x/y;
    }
}


4) La clase que devolverá la password de acceso al certificado usado para firmar los envíos del ws y para desencriptar los datos de las peticiones recibidas desde los clientes sería:

package es.org.cxf.ws.impl;

import java.io.IOException;

import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.UnsupportedCallbackException;

import org.apache.ws.security.WSPasswordCallback;

public class PasswordCallback implements CallbackHandler {

    public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {

        final WSPasswordCallback pc = (WSPasswordCallback)callbacks[0];
        pc.setPassword("password");
    }
}

5) Las clases de las peticiones y respuestas de cada uno de los métodos ofertados por el servicio web serían idénticas a las de la operación suma que adjuntamos:

package es.org.cxf.ws.impl.jaxws;

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;

@XmlRootElement(name = "suma", namespace = "http://impl.ws.cxf.org.es/")
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "suma", namespace = "http://impl.ws.cxf.org.es/", propOrder = {"arg0", "arg1"})
public class Suma {

    @XmlElement(name = "arg0")
    private int arg0;
    @XmlElement(name = "arg1")
    private int arg1;

    public int getArg0() {
        return this.arg0;
    }
    public void setArg0(int newArg0)  {
        this.arg0 = newArg0;
    }
    public int getArg1() {
        return this.arg1;
    }
    public void setArg1(int newArg1)  {
        this.arg1 = newArg1;
    }
}


package es.org.cxf.ws.impl.jaxws;

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;
 

@XmlRootElement(name = "sumaResponse", namespace = "http://impl.ws.cxf.org.es/")
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "sumaResponse", namespace = "http://impl.ws.cxf.org.es/")
public class SumaResponse {

    @XmlElement(name = "return")
    private int _return;
    public int getReturn() {
        return this._return;
    }
    public void setReturn(int new_return)  {
        this._return = new_return;
    }
}


6) El applicationContext.xml sería:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:jaxws="http://cxf.apache.org/jaxws"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://cxf.apache.org/jaxws http://cxf.apache.org/schemas/jaxws.xsd">

    <import resource="classpath:META-INF/cxf/cxf.xml" />
    <import resource="classpath:META-INF/cxf/cxf-extension-soap.xml" />
    <import resource="classpath:META-INF/cxf/cxf-servlet.xml" />

    <bean id="firmaPeticion" class="org.apache.cxf.ws.security.wss4j.WSS4JInInterceptor">
        <constructor-arg>
            <map>
                <entry key="action" value="Timestamp Signature Encrypt" />
                <!-- Ajustamos la verificación de la firma de las peticiones que nos llegan -->
                <entry key="signaturePropFile" value="props/server-trust-store.properties" />
                <!-- Ajustamos el desencriptado de los datos de la petición que recibimos -->
                <entry key="decryptionPropFile" value="props/server-key-store.properties" />
                <entry key="passwordCallbackClass" value="es.org.cxf.ws.sec.PasswordCallback" />
            </map>
        </constructor-arg>
    </bean>

    <bean id="firmaRespuesta" class="org.apache.cxf.ws.security.wss4j.WSS4JOutInterceptor">
        <constructor-arg>
            <map>
                <entry key="action" value="Timestamp Signature Encrypt" />
                <!-- Ajustamos el firmado de las respuestas -->
                <entry key="signaturePropFile" value="props/server-key-store.properties" />
                <entry key="user" value="desarrollo-internet" />
                <!-- Ajustamos el encriptado de las respuestas -->
                <entry key="encryptionPropFile" value="props/server-trust-store.properties" />
                <entry key="encryptionUser" value="edugarci" />
                <entry key="signatureKeyIdentifier" value="DirectReference" />
                <entry key="passwordCallbackClass" value="es.org.cxf.ws.sec.PasswordCallback" />
                <entry key="signatureParts" value="{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" />
                <entry key="encryptionParts" value="{Element}{http://www.w3.org/2000/09/xmldsig#}Signature;{Content}{http://schemas.xmlsoap.org/soap/envelope/}Body" />
                <entry key="encryptionKeyTransportAlgorithm" value="http://www.w3.org/2001/04/xmlenc#rsa-1_5" />
                <entry key="encryptionSymAlgorithm" value="http://www.w3.org/2001/04/xmlenc#tripledes-cbc" />
            </map>
        </constructor-arg>
    </bean>

    <jaxws:endpoint xmlns:tns="http://impl.ws.cxf.org.es/"
        id="calculadora" implementor="es.org.cxf.ws.impl.CalculadoraImpl"
        wsdlLocation="wsdl/calculadoraimpl.wsdl" endpointName="tns:CalculadoraImplPort"
        serviceName="tns:CalculadoraImplService" address="/CalculadoraImplPort">

        <jaxws:outInterceptors>
            <bean class="org.apache.cxf.binding.soap.saaj.SAAJOutInterceptor" />
            <ref bean="firmaRespuesta" />
        </jaxws:outInterceptors>
        <jaxws:inInterceptors>
            <ref bean="firmaPeticion" />
            <bean class="org.apache.cxf.binding.soap.saaj.SAAJInInterceptor" />
        </jaxws:inInterceptors>
       
    </jaxws:endpoint>
</beans>


7) El log4j.properties sería:

log4j.rootCategory=INFO,CONSOLA

log4j.logger.es.org.cxf.ws=INFO

org.apache.cxf.interceptor.LoggingInInterceptor.level=INFO
org.apache.cxf.interceptor.LoggingOutInterceptor.level=INFO
org.apache.cxf.ws.rm.security.wss4j.WSS4JInInterceptor.level=INFO
org.apache.cxf.ws.rm.security.wss4j.WSS4JOutInterceptor.level=INFO

log4j.appender.CONSOLA=org.apache.log4j.ConsoleAppender
log4j.appender.CONSOLA.layout=org.apache.log4j.PatternLayout
log4j.appender.CONSOLA.layout.ConversionPattern=%d %p [%c] - <%m>%n

log4j.appender.FICHERO=org.apache.log4j.RollingFileAppender
log4j.appender.FICHERO.file=${log4j.debug.ruta_fichero}/cxf-web.log
log4j.appender.FICHERO.append=true
log4j.appender.FICHERO.MaxFileSize=${log4j.debug.MaxFileSize}
log4j.appender.FICHERO.MaxBackupIndex=${log4j.debug.MaxBackupIndex}
log4j.appender.FICHERO.layout=org.apache.log4j.PatternLayout
log4j.appender.FICHERO.layout.ConversionPattern=[%d{dd-MM-yyyy HH\:mm\:ss,SSS}] thread\:[%t] nivelTraza\:[%p] lugarTraza\:[%c] mensajeTraza[%m]%n


8) El web.xml sería:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" id="WebApp_ID" version="2.5">
  <display-name>test-cxf-web</display-name>
  <servlet>
    <description>Apache CXF Endpoint</description>
    <display-name>cxf</display-name>
    <servlet-name>cxf</servlet-name>
    <servlet-class>org.apache.cxf.transport.servlet.CXFServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
  </servlet>
  <servlet-mapping>
    <servlet-name>cxf</servlet-name>
    <url-pattern>/services/*</url-pattern>
  </servlet-mapping>
  <session-config>
    <session-timeout>60</session-timeout>
  </session-config>
  <context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:/applicationContext.xml</param-value>
  </context-param>
  <listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
  </listener>
</web-app>


9) El keystore sería un fichero llamado server-keystore.jks donde se ha generado un certificado llamado "desarrollo-internet". Dejamos el fichero en la carpeta src/main/resources/keystore/server-keystore.jks

10) Precisamos de un fichero server-key-store.properties donde alojar información sobre el server-keystore.jks. Este fichero estará alojado en src/main/resources/props/server-key-store.properties. El contenido del mismo sería:

org.apache.ws.security.crypto.provider=org.apache.ws.security.components.crypto.Merlin
org.apache.ws.security.crypto.merlin.keystore.type=jks
org.apache.ws.security.crypto.merlin.keystore.password=password
org.apache.ws.security.crypto.merlin.keystore.alias=desarrollo-internet
org.apache.ws.security.crypto.merlin.keystore.file=keystore/server-keystore.jks


11) El truststore sería un fichero llamado server-truststore.jks donde se ha almacenado la clave pública del cliente que nos enviará peticiones firmadas. La clave pública de este certificado la llamamos "edugarci". Dejamos el fichero en la carpeta src/main/resources/keystore/server-truststore.jks

12) Precisamos de un fichero server-trust-store.properties donde alojar información sobre el server-truststore.jks. Este fichero estará alojado en src/main/resources/props/server-trust-store.properties. El contenido del mismo sería:

org.apache.ws.security.crypto.provider=org.apache.ws.security.components.crypto.Merlin
org.apache.ws.security.crypto.merlin.keystore.type=jks
org.apache.ws.security.crypto.merlin.keystore.password=password
org.apache.ws.security.crypto.merlin.keystore.alias=edugarci
org.apache.ws.security.crypto.merlin.keystore.file=keystore/server-truststore.jks




Módulo jar cxf-client

1) El fichero pom.xml de este módulo sería:

<project xmlns="http://maven.apache.org/POM/4.0.0" 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 

    http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <artifactId>cxf</artifactId>
        <groupId>es.org.sgtic</groupId>
        <version>1.0</version>
    </parent>
    <groupId>es.org.sgtic</groupId>
    <artifactId>cxf-client</artifactId>
    <version>1.0</version>
    <dependencies>
        <dependency>
            <groupId>org.apache.ws.security</groupId>
            <artifactId>wss4j</artifactId>
            <version>1.6.7</version>
            <type>jar</type>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>org.apache.cxf</groupId>
            <artifactId>cxf-rt-frontend-jaxws</artifactId>
            <version>2.6.3</version>
        </dependency>
        <dependency>
            <groupId>org.apache.cxf</groupId>
            <artifactId>cxf-rt-transports-http</artifactId>
            <version>2.6.3</version>
        </dependency>
        <dependency>
            <groupId>org.apache.cxf</groupId>
            <artifactId>cxf-rt-transports-http-jetty</artifactId>
            <version>2.6.3</version>
        </dependency>
        <dependency>
            <groupId>org.apache.cxf</groupId>
            <artifactId>cxf-rt-ws-rm</artifactId>
            <version>2.6.3</version>
        </dependency>
        <dependency>
            <groupId>org.apache.cxf</groupId>
            <artifactId>cxf-rt-ws-security</artifactId>
            <version>2.6.3</version>
        </dependency>
        <dependency>
            <groupId>org.apache.cxf</groupId>
            <artifactId>cxf-rt-ws-addr</artifactId>
            <version>2.6.3</version>
        </dependency>
        <dependency>
            <groupId>org.apache.cxf</groupId>
            <artifactId>cxf-rt-ws-policy</artifactId>
            <version>2.6.3</version>
        </dependency>
    </dependencies>
</project>


2) El wsdl del servicio web a atacar estará publicado en la URL:

http://localhost:8080/cxf-web/services/CalculadoraImplPort

3) El contenido del wsdl sería:

<?xml version="1.0" encoding="UTF-8"?>
<wsdl:definitions name="CalculadoraImplService" targetNamespace="http://impl.ws.cxf.org.es/" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:tns="http://impl.ws.cxf.
org.es/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/">
  <wsdl:types>
    <schema xmlns="http://www.w3.org/2001/XMLSchema">
<import namespace="http://impl.ws.cxf.org.es/" schemaLocation="calculadoraimpl_schema1.xsd"/>
</schema>
  </wsdl:types>
  <wsdl:message name="restaResponse">
    <wsdl:part name="parameters" element="tns:restaResponse">
    </wsdl:part>
  </wsdl:message>
  <wsdl:message name="multiplica">
    <wsdl:part name="parameters" element="tns:multiplica">
    </wsdl:part>
  </wsdl:message>
  <wsdl:message name="suma">
    <wsdl:part name="parameters" element="tns:suma">
    </wsdl:part>
  </wsdl:message>
  <wsdl:message name="resta">
    <wsdl:part name="parameters" element="tns:resta">
    </wsdl:part>
  </wsdl:message>
  <wsdl:message name="divide">
    <wsdl:part name="parameters" element="tns:divide">
    </wsdl:part>
  </wsdl:message>
  <wsdl:message name="multiplicaResponse">
    <wsdl:part name="parameters" element="tns:multiplicaResponse">
    </wsdl:part>
  </wsdl:message>
  <wsdl:message name="sumaResponse">
    <wsdl:part name="parameters" element="tns:sumaResponse">
    </wsdl:part>
  </wsdl:message>
  <wsdl:message name="divideResponse">
    <wsdl:part name="parameters" element="tns:divideResponse">
    </wsdl:part>
  </wsdl:message>
  <wsdl:portType name="Calculadora">
    <wsdl:operation name="resta">
      <wsdl:input name="resta" message="tns:resta">
    </wsdl:input>
      <wsdl:output name="restaResponse" message="tns:restaResponse">
    </wsdl:output>
    </wsdl:operation>
    <wsdl:operation name="divide">
      <wsdl:input name="divide" message="tns:divide">
    </wsdl:input>
      <wsdl:output name="divideResponse" message="tns:divideResponse">
    </wsdl:output>
    </wsdl:operation>
    <wsdl:operation name="suma">
      <wsdl:input name="suma" message="tns:suma">
    </wsdl:input>
      <wsdl:output name="sumaResponse" message="tns:sumaResponse">
    </wsdl:output>
    </wsdl:operation>
    <wsdl:operation name="multiplica">
      <wsdl:input name="multiplica" message="tns:multiplica">
    </wsdl:input>
      <wsdl:output name="multiplicaResponse" message="tns:multiplicaResponse">
    </wsdl:output>
    </wsdl:operation>
  </wsdl:portType>
  <wsdl:binding name="CalculadoraImplServiceSoapBinding" type="tns:Calculadora">
    <soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http"/>
    <wsdl:operation name="resta">
      <soap:operation soapAction="urn:Resta" style="document"/>
      <wsdl:input name="resta">
        <soap:body use="literal"/>
      </wsdl:input>
      <wsdl:output name="restaResponse">
        <soap:body use="literal"/>
      </wsdl:output>
    </wsdl:operation>
    <wsdl:operation name="divide">
      <soap:operation soapAction="urn:Divide" style="document"/>
      <wsdl:input name="divide">
        <soap:body use="literal"/>
      </wsdl:input>
      <wsdl:output name="divideResponse">
        <soap:body use="literal"/>
      </wsdl:output>
    </wsdl:operation>
    <wsdl:operation name="suma">
      <soap:operation soapAction="urn:Suma" style="document"/>
      <wsdl:input name="suma">
        <soap:body use="literal"/>
      </wsdl:input>
      <wsdl:output name="sumaResponse">
        <soap:body use="literal"/>
      </wsdl:output>
    </wsdl:operation>
    <wsdl:operation name="multiplica">
      <soap:operation soapAction="urn:Multiplica" style="document"/>
      <wsdl:input name="multiplica">
        <soap:body use="literal"/>
      </wsdl:input>
      <wsdl:output name="multiplicaResponse">
        <soap:body use="literal"/>
      </wsdl:output>
    </wsdl:operation>
  </wsdl:binding>
  <wsdl:service name="CalculadoraImplService">
    <wsdl:port name="CalculadoraImplPort" binding="tns:CalculadoraImplServiceSoapBinding">
      <soap:address location="http://localhost:8080/cxf-web/services/CalculadoraImplPort"/>
    </wsdl:port>
  </wsdl:service>
</wsdl:definitions>


4) Las clases del cliente se generarían a partir del wsdl mediante el asistente incorporado en Eclipse Helios. Hay una peculiaridad y es que para que el asistente genere las clases necesita trabajar sobre un proyecto web, así pues montamos un proyecto web (servlets 2.5) vacío, copiamos el fichero wsdl a este proyecto y haciendo clic sobre el con el botón derecho del ratón seleccionamos la opción "Web Services/Generate Client". En el asistente que se lanza definimos que el "server runtime" sea Tomcat y el "webservice runtime" sea Apache CXF.

5) Las clases generadas las moveremos al proyecto cxf-client y ya podríamos borrar el proyecto web comodín. El contenido de estas clases excede el objetivo del blog, pero la lista de clases sería:

CalculadoraImpl.java
Multiplica.java 
RestaResponse.java
CalculadoraImplService.java

MultiplicaResponse.java
Suma.java
Calculadora.java

ObjectFactory.java
SumaResponse.java
Divide.java

package-info.java
DivideResponse.java

Resta.java

6) La clase que devolverá la password de acceso al certificado usado por el cliente para firmar los envíos al ws y para desencriptar las respuestas recibidas desde el ws sería:

package es.org.cxf.ws.sec;

import java.io.IOException;

import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.UnsupportedCallbackException;

import org.apache.ws.security.WSPasswordCallback;

public class PasswordCallback implements CallbackHandler {

    public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {

        //Password del certificado "edugarci" de "client-keystore.jks" se se
        //usa para firmar los envíos del cliente y desencriptar los mensajes
        //que le llegan cifrados desde el servidor con la clave publica de "edugarci"
        final WSPasswordCallback pc = (WSPasswordCallback)callbacks[0];
        pc.setPassword("password");
    }
}

7) La clase ejemplo donde se implementa mediante el API todo el tema de cifrado y firmado del lado del cliente sería:

package es.ins.sgtic.app;

import java.net.URL;
import java.util.HashMap;
import java.util.Map;

import javax.xml.namespace.QName;

import org.apache.cxf.binding.soap.saaj.SAAJInInterceptor;
import org.apache.cxf.binding.soap.saaj.SAAJOutInterceptor;
import org.apache.cxf.endpoint.Client;
import org.apache.cxf.endpoint.Endpoint;
import org.apache.cxf.frontend.ClientProxy;
import org.apache.cxf.ws.security.wss4j.WSS4JInInterceptor;
import org.apache.cxf.ws.security.wss4j.WSS4JOutInterceptor;

import es.org.cxf.ws.impl.Calculadora;
import es.org.cxf.ws.impl.CalculadoraImplService;

public class AppTest {

    private static final QName SERVICE_NAME = new QName("http://impl.ws.cxf.org.es/", "CalculadoraImplService");
   
    public static void main(String[] args) {

        //Definimos el servicio web a usar
        final URL wsdlURL = CalculadoraImplService.WSDL_LOCATION;
        final CalculadoraImplService calcService = new CalculadoraImplService(wsdlURL, SERVICE_NAME);
        final Calculadora calculadora = calcService.getCalculadoraImplPort();
               
        //Obtenemos la referencia al endpoint del ws
        final Client client = ClientProxy.getClient(calculadora);
        final Endpoint cxfEndpoint = client.getEndpoint();
       
        //Configuramos el interceptor de cifrado/firmado para los envíos
        final Map<String,Object> outProps = new HashMap<String,Object>();
        outProps.put("action", "Timestamp Signature Encrypt");
        outProps.put("user", "edugarci");
        outProps.put("signaturePropFile", "props/client-key-store.properties");
        outProps.put("signatureKeyIdentifier", "DirectReference");
        outProps.put("passwordCallbackClass", "es.org.cxf.ws.sec.PasswordCallback");
        outProps.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");
        outProps.put("encryptionPropFile", "props/client-trust-store.properties");
        outProps.put("encryptionUser", "desarrollo-internet");
        outProps.put("encryptionParts", "{Element}{http://www.w3.org/2000/09/xmldsig#}Signature;{Content}{http://schemas.xmlsoap.org/soap/envelope/}Body");
        outProps.put("encryptionSymAlgorithm", "http://www.w3.org/2001/04/xmlenc#tripledes-cbc");
        outProps.put("encryptionKeyTransportAlgorithm", "http://www.w3.org/2001/04/xmlenc#rsa-1_5");
        WSS4JOutInterceptor wssOut = new WSS4JOutInterceptor(outProps); //request
        cxfEndpoint.getOutInterceptors().add(wssOut);
        cxfEndpoint.getOutInterceptors().add(new SAAJOutInterceptor());
       
        //Configuramos el interceptor de descifrado/verificacion-firma para las recepciones
        Map<String,Object> inProps= new HashMap<String,Object>();
        inProps.put("action", "Timestamp Signature Encrypt");
        inProps.put("signaturePropFile", "props/client-trust-store.properties");
        inProps.put("passwordCallbackClass", "es.org.cxf.ws.sec.PasswordCallback");
        inProps.put("decryptionPropFile", "props/client-key-store.properties");
        WSS4JInInterceptor wssIn = new WSS4JInInterceptor(inProps); //response
        cxfEndpoint.getInInterceptors().add(wssIn);
        cxfEndpoint.getInInterceptors().add(new SAAJInInterceptor());
       
        //Lanzamos unas peticiones de ejemplo
        for (int c=0;c<100;c++){
            System.out.println(String.format("port.suma(15, 3) --> %d",calculadora.suma(15, 3)));
            System.out.println(String.format("port.resta(15, 3) --> %d",calculadora.resta(15, 3)));
            System.out.println(String.format("port.multiplica(15, 3) --> %d",calculadora.multiplica(15, 3)));
            System.out.println(String.format("port.divide(15, 3) --> %d",calculadora.divide(15, 3)));
        }
    }
}