Como crear Componente Composite JSF para Cliente @firma

Esta entrada va de cómo hacer un componente JSF para llevar a nuestras aplicaciones el cliente de @firma 3.3.1 de forma homogénea en todas ellas y haciendo que esta tarea no se convierta en un quebradero de cabeza cada vez que queremos hacerlo.

EjecucónClienteAFirma3.3.1

También ilustra cómo crear componentes JSF en base a páginas xhtml, lo cual resulta sencillo, y creo que muy útil. El entorno que se trabaja en el artículo es el siguiente:

Pasos a seguir:

Paso 1. Conseguir la distribución del Cliente @Firma 3.3.1

Paso 2. Averiguar cómo funciona el Cliente

Paso 3. Hacer el Componente y registrarlo en el contenedor

Paso 4. Ubicar los archivos de la distribución del Cliente para su descarga

Paso 5. Probarlo (código fuente en GitHub)

Paso 1. Conseguir la distribución del Cliente @Firma 3.3.1

Desde que se liberó el código fuente del cliente @firma la distribución se puede descargar desde la Forja del Centro de Transferencia Tecnológica y aquí teneis el enlace directo para descargar el ZIP con la Demo y la distribucion.

Paso 2. Averiguar cómo funciona el Cliente @Firma 3.3.1

Para esto es recomendable la lectura del manual del integrador, pero para el que no quiera hacerlo ahora, os hago un resumen, pero recomiendo encarecidamente su lectura.

El cliente de @firma es un applet que se ejecutará en el ordenador del usuario que navega por nuestra aplicación. La distribución del cliente incorpora unos javascripts que haciendo document.write incluyen el applet en el código fuente de la página que se está renderizando. Dado que se trata de un applet, éste debe ser descargado por el navegador, por lo que la distribución del cliente deben estar disponibles para su descarga directa en nuestra aplicación. Para activarlo es necesario invocar a una función de carga que se define en los scripts del cliente.

Esta versión del Cliente permite, además de firmar datos, hash y web (CMS/PKCS#7, CAdES, XAdES Detached, XAdES Enveloped, XAdES Enveloping, XMLDSig Detached, XMLDSig Enveloped, XMLDSig Enveloping), realizar firma de ficheros PDF (PAdES), ODF, OOXML.

Otras características que tiene este cliente es que permite la incorporación de la imagen de rúbrica a los ficheros PDF e incluso habilita mecanismos para realizar firma masiva de directorios completos y almacenar las firmas en el disco del ordenador del usuario, si necesitáis de estas funcionalidades os recomiendo que veáis las páginas de demo incluidas en el ZIP y en el manual del integrador.

Paso 3. Hacer un Composite Component

Un paso previo a incorporar las funciones del cliente a un tag JSF basado en páginas XHTML es conocer cómo se hace uno, bien… pues allá va

JSF utiliza el espacio de nombres http://java.sun.com/jsf/composite/ para permitirnos hacer tags personalizados, si no necesitamos que nuestro componente tenga un funcionamiento complejo, con estado y métodos que responden a eventos o que manipulan ese estado, etc. nos bastará con definir una plantilla para nuestro componente en forma de página XHTML y situarla en un determinado lugar para que el contenedor la encuentre cuando queramos utilizar el componente.

Esta plantilla puede ubicarse en dos sitios

  1. webapp_root/resources/componente/plantilla.xhtml
  2. classpath:/META-INF/resources/componente/plantilla.xhtml

Si por ejemplo, componente = ‘clientefrma’ y plantilla = ‘cliente331-mfm.xhtml’

El espacio de nombres para usar nuestro componente será http://java.sun.com/jsf/composite/clientefirma

La forma de usarlo en nuestro código será así

	<ui:define name="content">
	<h:form onsubmit="return false;">
		<cfirma:cliente331-scripts />
		<cfirma:cliente331-mfm functionName="firmarXAdES" signFormat="XAdES Detached" returnOriginalData="true" />
		<cfirma:cliente331-mfm functionName="firmarCAdES" signFormat="CAdES" returnOriginalData="false" />

		<h1>Demo firma</h1>
		<button name="firmarX" onclick="$('#resultado').val(JSON.stringify(firmarXAdES(jsonData))); return false;" value="Firmar XAdES" type="button">Firmar XAdES</button>
		<button name="firmarC" onclick="$('#resultado').val(JSON.stringify(firmarCAdES(jsonData))); return false;" value="Firmar CAdES" type="button">Firmar CAdES</button>
		<br/>
		Datos a firmar<br/>
		<textarea id="datosAfirmar" name="datosAfirmar" cols="50" rows="5" contenteditable="false" ></textarea>
		<br/>Resultado<br/>
		<textarea id="resultado" cols="50" rows="25" ></textarea>
	</h:form>
	</ui:define>

Implementación del componente

La implementación del componente se realiza en los archivos de plantilla XHTML, la plantilla se divide en dos partes el interfaz y la implementación. En la parte de interfaz se define cómo será el componente de cara al exterior (obvio, no?) se definen los atributos que tendrán, se documentan, se define su tipo, su obligatoriedad y más. La implementación es el código que sustituirá a nuestro componente en dónde lo usemos.

Tooltip con ayuda sobre el Composite Component

Tooltip con ayuda sobre el Composite Component

En el caso de esta versión del Cliente @Firma he considerado necesario hacer dos componentes, uno para cargar el applet y otro para definir funciones de firma. De esta forma en una misma página se podría usar el cliente para firmar distintos elementos con distintas configuraciones. El componente de definición de funciones de firma usa de firma usa la multifirma masiva del cliente, y los datos a firmar los espera en una estructura JSON, de la forma siguiente:


[

{"id":"1","typeData":"hash","datosB64":"QUhLU0RIS0tILi4u"},

"id":"2","typeData":"data","datosB64":"bHNramZsayBhc2Zsc2FramRmIGxrc2pk8WxmayBqc/FkZmxqc2HxbGRmbGFz"}

]

Y devolverá una estructura como la siguiente con el resultado de las firmas, incluyendo, opcionalmente, los datos originales

	var massiveResult = {
		"certificadoB64" :"",
		"signatureAlg" : "",
		"signatureFormat" : "",
		"signatureMode" : "",
		"originalFormat" : "",
		"massiveOperation" : "",
		"firmas" : [
			{ "id" : "1",
			  "typeData" : "",
			  "datosB64": "",
			  "firmaB64" : "" },
			{ "id" : "2",
			  "typeData" : "",
			  "datosB64": "",
			  "firmaB64" : "" },...
			]
	};

Componente para carga del Cliente @Firma

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
	xmlns:h="http://java.sun.com/jsf/html"
	xmlns:f="http://java.sun.com/jsf/core"
	xmlns:ui="http://java.sun.com/jsf/facelets"
	xmlns:composite="http://java.sun.com/jsf/composite">
<composite:interface componentType="AFirmaJSFComponent" name="cliente331-scripts"
	displayName="Scripts Cliente @firma 3.3.1"
	shortDescription="Scripts de carga el componente para firma electrónica 3.3.1" >
		<composite:attribute name="load" required="false" default="true" shortDescription="Hace las funciones de 'rendered'" />
</composite:interface>
<composite:implementation>
<h:panelGroup  layout="span" rendered="#{cc.attrs.load}">
	<script type="text/javascript" language="javascript" src="#{resource['clienteFirma3_3_1:common-js/deployJava.js']}"></script>
	<script type="text/javascript" language="javascript" src="#{resource['clienteFirma3_3_1:common-js/firma.js']}"></script>
	<script type="text/javascript" language="javascript" src="#{resource['clienteFirma3_3_1:common-js/instalador.js']}"></script>
	<script type="text/javascript" language="javascript" src="#{resource['clienteFirma3_3_1:constantes.js']}"></script>
	<script type="text/javascript" >&amp;lt;!--
		var baseDownloadURL = location.protocol+"//"+location.host+"#{facesContext.externalContext.requestContextPath}/clienteFirma3_3_1";
		var base = location.protocol+"//"+location.host+"#{facesContext.externalContext.requestContextPath}/clienteFirma3_3_1";
		if(clienteFirma == undefined)
			cargarAppletFirma('COMPLETA');
	--></script>
</h:panelGroup>
</composite:implementation>
</html>

Componente para definición de funciones de firma

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
	xmlns:h="http://java.sun.com/jsf/html"
	xmlns:f="http://java.sun.com/jsf/core"
	xmlns:ui="http://java.sun.com/jsf/facelets"
	xmlns:composite="http://java.sun.com/jsf/composite">
<composite:interface componentType="AFirmaJSFComponent" name="cliente331-mfm"
	displayName="Funcion de multifirma masiva con Cliente @firma 3.3.1"
	shortDescription="Configura una función javascript para firmar con el cliente de firma electrónica @firma 3.3.1" >

<!-- Parámetros de configuracion de la firma -->
	<composite:attribute name="signFormat" default="CMS/PKCS#7"
		required="false"
		shortDescription="Formato de firma, por defecto CMS/PKCS#7 [CMS/PKCS#7 | CAdES | XAdES Detached | XAdES Enveloped | XAdES Enveloping | XMLDSig Detached | XMLDSig Enveloped | XMLDSig Enveloping | PAdES | ODF | OOXML]" />
	<composite:attribute name="encryptAlg" default="SHA1withRSA"
		required="false"
		shortDescription="Algoritmo de encriptación, por defecto SHA1withRSA [SHA1withRSA | MD5withRSA | MD2withRSA]" />
	<composite:attribute name="signMode" default="explicit"
		required="false"
		shortDescription="Establece el formato de la firma, por defecto explicit [explicit | implicit]. Solo determinados formatos de firma admiten esta configuración: CMS/PKCS#7, CAdES, XAdES Detached, XAdES Enveloped, XMLDSig Detached, XMLDSig Enveloped " />
	<composite:attribute name="originalFormat" default="true"
		required="false"
		shortDescription="Indica si se debe respetar el formato de firma original para las operaciones de multifirma masiva, por defecto true [true | false]." />
	<composite:attribute name="massiveOperation" default="FIRMAR"
		required="false"
		shortDescription="Establece la operación masiva a realizar, por defecto FIRMAR [FIRMAR | COFIRMAR | CONTRAFIRMAR_ARBOL | CONTRAFIRMAR_HOJAS]" />
	<composite:attribute name="returnOriginalData" default="false"
		required="false"
		shortDescription="Hace que se devuelvan o no los datos originales en el resultado de la operacion de firma, por defecto false [true | false]" />

<!-- Propiedades generales del componente -->
	<composite:attribute name="load" required="false" default="true" shortDescription="Hace las funciones de 'rendered'" />
	<composite:attribute name="okMessage" default="Firma realizada correctamente" required="false" shortDescription="Mensaje que se mostrará cuando la firma se realice correctamente" />
	<composite:attribute name="failMessage" default="No se ha completado la firma" required="false" shortDescription="Mensaje que se mostrará cuando haya algun problema al firmar" />
	<composite:attribute name="functionName" default="firmaConApplet" required="false" shortDescription="Nombre de la función javascript que realizará la firma" />
</composite:interface>
<composite:implementation>
<h:panelGroup id="funcionFirmaScript3_3_1" layout="span" rendered="#{cc.attrs.load}" >
		<script type="text/javascript">&amp;lt;!--
		/**
		 * jsonData: Objeto JSON con los datos a firmar
		 *		var jsonData = {[
		 *						{"id" : "1", "typeData" : "hash", "datosB64": "AHKSDHKKH..."},
		 *         				{"id" : "2", "typeData" : "hash", "datosB64": "AHKSDHKKH..."}, ...
		 *						]};
		 * okMessage: Mensaje que se muestra al realizar la firma correctamente.
		 * failMessge: Mensaje que se muestra al no realizarse la firma correctamente.
		 * Return: Devuelve una estructura JSON con los datos de la operación
		 *		var massiveResult = {
		 *			"certificadoB64" :"",
		 *			"signatureAlg" : "#{cc.attrs.encryptAlg}",
		 *			"signatureFormat" : "#{cc.attrs.signFormat}",
		 *			"signatureMode" : "#{cc.attrs.signMode}",
		 *			"originalFormat" : "#{cc.attrs.originalFormat}",
		 *			"massiveOperation" : "#{cc.attrs.massiveOperation}",
		 *			"firmas" : [
		 *				{ "id" : "1",
		 *				  "typeData" : "",
		 *				  "datosB64": "",
		 *				  "firmaB64" : "" },
		 *				{ "id" : "2",
		 *				  "typeData" : "",
		 *				  "datosB64": "",
		 *				  "firmaB64" : "" },...
		 *				]
		 *		};
		 *
		 */
		function #{cc.attrs.functionName}(jsonData, okMessage, failMessage) {
			// Comprobamos que se haya introducido al menos un fichero
			var filesList = document.getElementById('#{cc.attrs.data}');
			if(jsonData == undefined || jsonData == null) {
				alert("No se han indicado datos sobre los que realizar la operaci\u00F3n.");
				return;
			}
			var massiveResult = {
					"certificadoB64" :"",
					"signatureAlg" : "#{cc.attrs.encryptAlg}",
					"signatureFormat" : "#{cc.attrs.signFormat}",
					"signatureMode" : "#{cc.attrs.signMode}",
					"originalFormat" : "#{cc.attrs.originalFormat}",
					"massiveOperation" : "#{cc.attrs.massiveOperation}",
					"firmas" : new Array() /*[{
						"id" : "",
						"typeData" : "",
						"datosB64": "",
						"firmasB64" : "",
						}]*/
			};

			// Limpiamos la configuracion del cliente
			initialize();
			// Configuramos los datos de la operacion
			configuraFirma();
			clienteFirma.setSignatureAlgorithm('#{cc.attrs.encryptAlg}');
			clienteFirma.setSignatureFormat('#{cc.attrs.signFormat}');
			clienteFirma.setSignatureMode('#{cc.attrs.signMode}');
			clienteFirma.setOriginalFormat('#{cc.attrs.originalFormat}');
			clienteFirma.setMassiveOperation('#{cc.attrs.massiveOperation}');

			// Iniciamos la operacion masiva
			clienteFirma.initMassiveSignature();

			for(var i=0; i &amp;lt; jsonData.length; i++) {
				var data = jsonData[i];
				massiveResult.firmas[i] = {
						"id" : data.id,
						"typeData" : data.typeData,
						"datosB64" : #{cc.attrs.returnOriginalData} ? data.datosB64 : "",
						"firmaB64" : ""
				};
				var result;
				if(data.typeData == 'data') {
					result = clienteFirma.massiveSignatureData(data.datosB64);
				} else if(data.typeData == 'file') {
					result = clienteFirma.massiveSignatureFile(data.datosB64);
				} else if(data.typeData == 'hash') {
					result = clienteFirma.massiveSignatureHash(data.datosB64);
				}
				massiveResult.firmas[i].firmaB64 = result;
			}
			massiveResult.certificadoB64 = clienteFirma.getSignCertificateBase64Encoded();
			// Finalizamos la operacion masiva
			clienteFirma.endMassiveSignature();
			return massiveResult;
		}
	--></script>
</h:panelGroup>
</composite:implementation>
</html>

Registrar el componente en el contendor

Será necesario que nuestro componente se registre en el contenedor, para ello en JEE6 basta con tener una clase con la anotación @FacesComponent, nótese que el valor de la anotación es lo mismo que se indica en el los componentes en  


@FacesComponent("AFirmaJSFComponent")
public class AFirmaJSFComponent extends UINamingContainer implements Serializable {

/**
 * Serial uid.
 */
 private static final long serialVersionUID = 4324952100642134119L;

}

Si queremos que nuestro componente sea compatible con otros contenedores podemos incluir el fichero faces-config.xml

<?xml version="1.0"?>
<faces-config version="2.0" xmlns="http://java.sun.com/xml/ns/javaee"
	xmlns:xi="http://www.w3.org/2001/XInclude" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facesconfig_2_0.xsd">
	<component>
		<component-type>AFirmaJSFComponent</component-type>
		<component-class>com.saiyandev.faces.component.afirma.AFirmaJSFComponent</component-class>
	</component>
</faces-config>

La estructura final debería ser más o menos esta

Estructura componente JSF

Estructura del componente JSF para cliente @firma

Paso 4. Ubicar los archivos del Cliente @Firma para su descarga

Dado que se trata de varios megabytes no es recomendable hacer que se sirvan desde el classpath, por lo que los ubicaremos en el contenido de la aplicación web, como si se tratase de imágenes y hojas de estilo. El nombre del directorio en el que se ubiquen debe coincidir con el nombre de directorio que se indica en el archivo cliente331-scripts.xhtml 

...
var baseDownloadURL = location.protocol+"//"+location.host+"#{facesContext.externalContext.requestContextPath}/clienteFirma3_3_1";
var base = location.protocol+"//"+location.host+"#{facesContext.externalContext.requestContextPath}/clienteFirma3_3_1";
...

Estructura para Descarga del Cliente @Firma

Estructura de directorios para descarga del cliente @Firma

Estos archivos también pueden ser ubicados dentro del classpath de la aplicación web (WAR) en classpath:/META-INF/resources/clientefirma3_3_1, y el contenedor también los resolvería, pero como decía antes no creo adecuado que se sirvan desde classpath.

Esta forma de distribuir el componente podría ser una solución en un entorno complejo y amplio, puede ser incluir los archivos dentro del propio componente, pero sería necesario que el fichero jar del componente estuviese en el directorio WEB-INF/lib de la aplicación web, si se pone en la zona de carga de clases de una aplicación EAR, las aplicaciones web no podrán resolver los recursos.

Paso 5. Probarlo

Podéis encontrar el código fuente de este artículo en mi cuenta de GitHub: https://github.com/jotraverso/firma-electronica

Espero que os resulte útil.

Firmando con Certificado

Firmando con Certificado

Deja un comentario