Web Service Security

8
90474

WSS (Web Service Security) – Autenticación por usuario y contraseña

Índice de contenido

Introducción

En este tutorial vamos a ver como crear un servicio web seguro con autenticación mediante usuario y contraseña.
En primer lugar tenemos que tener en cuenta que WS-Security no es un nuevo tipo de servicios web ni de
seguridad. WS-Security define cómo utilizar los tokens de seguridad, XML Signature y Xml Encryption en los
mensajes SOAP para proporcionar autenticación, confidencialidad e integridad a los Servicios Web.

En el caso que nos ocupa, vamos a centrarnos únicamente en la autenticación del usuario mediante un token de usuario y contraseña.
Debemos saber que en WS-Security también existen otros tipos de autenticación basados en certificados X509, tickets de Kerberos, etc.
que no vamos a ver en este tutorial.

Entorno

El tutorial está escrito usando el siguiente entorno:

Instalación de Axis2.

En este tutorial de nuestro compañero
Iván nos explica la creación de servicios web con Axis2,
por lo que toda la preparación del entorno es la misma, así que sólamente vamos a recordar brévemente la instalación del módulo web de Axis2
que nos va a servir como motor de servicios web.

Nos descargarmos la distribución en WAR de Axis2 que viene comprimida, y lo único que tenemos que hacer es descomprimir el fichero «axis2-1.4.1-war.zip»
y copiar en el directorio «webapps» de nuestro tomcat el fichero axis2.war. Al arrancar el tomcat desplegará esta aplicación y ya tendremos nuestro motor de servicios
web funcionando.

Para comprobar que todo ha ido correctamente podemos ir a http://localhost:8080/axis2/axis2-web/HappyAxis.jsp y comprobar que todo funciona correctamente.

NOTA: Si teneís instalado algún antivirus, es posible que os corte la comunicación de las peticiones a los servicios web. No se exáctamente porqué, pero en mi caso las peticiones http a páginas web
funcionaban correctamente, pero las peticiones a servicios web no lo hacían, por lo que tuve que cambiar la configuración del antivirus. Se puede detectar si en la página «HappyAxis» os sale un mensaje como este:


O si ejecutáis un cliente java podéis obtener un error como éste:

     org.apache.axis2.AxisFault: com.ctc.wstx.exc.WstxEOFException: Unexpected EOF in prolog
     at [row,col {unknown-source}]: [1,0]
     ...
    

Ya sabéis, cosas de los antivirus :(.

Hasta aquí lo que hemos conseguido es tener la instalación de servicios web normal, pero nos falta incluirle las extensiones que soporten WS-Security. Para esto nos vamos ha descargar
la implementación a href=»http://ws.apache.org/rampart/»>Apache Rampart, concretamente la versión 1.4. Este módulo
es el módulo de seguridad para axis2, y en su página podéis encontrar toda la especificación del mismo.

Para instalar el módulo de Rampart, descomprimimos el fichero ZIP que nos hemos descargado, y lo único que tenemos que hacer es copiarnos todas las librerías (JAR’s) del directorio «lib»
de la distribución de Rampart al directorio «WEB-INF/lib» de nuestra instalación de Axis2 y los ficheros del directorio «modules» de la distribución de Rampart al directorio «WEB-INF/modules»
de la instalación de Axis2.

Ahora ya tenemos instalado nuestro motor de servicios web con el soporte de seguridad definido en WS-Security.

Creación del servicio.

Para crear el servicio vamos a crearnos un nuevo proyecto de Maven, de esta forma podemos aprovechar no sólo la gestión de dependencias, también utilizaremos el plugin de empaquetamiento
para generar archivos «.aar» (Axis ARchive, equivalente a los «.jar»). Si quereís ver como se instala el plugin de Maven (Q4E) para eclipse podéis ver
este tutorial de nuestro compañero Alejandro.

Creamos un nuevo proyecto de Maven para el servicio web, seleccionamos

El plugin nos generará la siguiente estructura, donde vamos a eliminar las clases «App.java» y «AppTest.java» que nos crea.

Modicamos el pom.xml

Ahora vamos a modificar el fichero «pom.xml» para utilizar el plugin «axis2-aar-maven-plugin».
Con este plugin vamos a empaquetar el proyecto directamente como un fichero «aar» que podremos desplegar como servicio web directamente. Podéis ver cómo indicamos que el fichero «services.xml»
que crearemos en el directorio «src/main/config» lo incluya en el directorio «META-INF» y servira como descriptor del servicio para Axis2.

También crearemos la dependencia con WSS4J, necesaria para incluir la seguridad a nuestro servicio web.


	4.0.0
	com.autentia.wss.usertoken
	WSSUserTokenService
	aar
	1.0-SNAPSHOT
	WSSUserTokenService
	http://maven.apache.org
	
		
			
				org.apache.axis2
				axis2-aar-maven-plugin
				1.4.1
				true
				
					
						
							
								src/main/config/
							
							META-INF
							
								services.xml
							
						
					
				
			
		
	
	
		
			org.apache.ws.security
			wss4j
			1.5.4
			provided
		
	

    

Clase del servicio

Ahora vamos a crear la clase que va a proporcionar el servicio. En este caso es un servicio de echo «Echo.java»,
que como véis no tiene ningún misterio, donde indicaremos la configuración de seguridad será en el fichero descriptor
del servicio.

package com.autentia.wss.usertoken.service;

/**
 * 

* Echo.java
Clase que implementa la logica de nuestro web service *

*/ public class Echo { /** * Metodo que implementa la funcionalidad de saludo * * @param nombre Nombre de la persona que invoca el servicio * @return Cadena de saludo */ public String saludar(String nombre) { return "Hola " + nombre; } /** * Metodo que implementa la funcionalidad de despedida * * @param nombre Nombre de la persona que invoca el servicio * @return Cadena de despedida */ public String despedir(String nombre) { return "Adios " + nombre; } }

Descriptor del servicio

Para desplegar el servicio en Axis2, nos creamos un fichero descriptor del mismo. En este caso seguimos la estructura que hemos indicado en nuestro fichero «pom.xml»,
y creamos el fichero «services.xml» en el directorio «src/main/config».

En este fichero, además de descripbir el servicio, vamos a incluir una política de seguridad para comprobar la autenticación de quién está llamando al servicio.
Esto lo hacemos en dos partes, una indicando la configuración de seguridad de autenticación mediante usuario y contraseña. Y otra configurando adecuadamente el módulo de Rampart,
que hemos visto que va a ser el encargado de resolver la seguridad de nuestro servicio web.

En la configuración de Rampart, indicaremos la clase responsable de comprobar los usuarios y contraseñas con el elemento «passwordCallBackClass».


	
		Web service que emite un saludo o una despedida con autenticación de usuario
	
	
		
	
	
		com.autentia.wss.usertoken.service.Echo
	
	
	

	

	
		
			
				
					
						
					
				
				
					
						com.autentia.wss.usertoken.service.PWCBHandler
					
				
			
		
	
    
    

Clase de comprobación de usuario y contraseña

Lo único que nos queda por implementar en nuestro servicio es la clase que comprueba el usuario y contraseña de las peticiones de servicio.
Nos creamo la clase «com.autentia.wss.usertoken.service.PWCBHandler» que hemos configurado en el descriptor del servicio para que se encargue de
dicha tarea. Esta clase debe heredar de «javax.security.auth.callback.CallbackHandler» y el tipo de «callback» que va a procesar es del tipo «org.apache.ws.security.WSPasswordCallback».

El código de nuestra clase es:

package com.autentia.wss.usertoken.service;

import org.apache.ws.security.WSPasswordCallback;

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

import java.io.IOException;

public class PWCBHandler implements CallbackHandler {

    public void handle(Callback[] callbacks) throws IOException,
            UnsupportedCallbackException {
        for (int i = 0; i < callbacks.length; i++) {
            //Autenticación del usuario y contraseña
            WSPasswordCallback pwcb = (WSPasswordCallback)callbacks[i];
            //comprobaciones de usuario y contraseña para acceso al servicio
            if(pwcb.getIdentifer().equals("autentia") && pwcb.getPassword().equals("password")) {
                //autenticación con éxito
                return;
            } else {
                throw new UnsupportedCallbackException(callbacks[i], "fallo de autenticación");
            }
        }
    }
    
}     
     

Despliegue del servicio

Para desplegar el servicio primero empaquetamos ejecutando el siguiente comando de Maven en la consola, situándonos en el directorio raíz del proyecto.

mvn package

Esto nos generará el fichero "WSSUserTokenService-1.0-SNAPSHOT.aar" en el directorio "target" de nuestro proyecto. Ahora sólo nos queda copiar dicho fichero
al directorio "WEB-INF/services" de la instalación de Axis2 y se desplegará nuestro servicio que podremos comprobar en la administración web de Axis2, y vemos que tiene vinculado el módulo de Rampart.

Creación del cliente.

Para crear el el cliente, nos creamos otro proyecto de Maven igual que hicimos con el servicio, donde vamos a cambiar el nombre (lógicamente) por "WSSUserTokenClient" y el paquete de las clases por "com.autentia.wss.usertoken.client".

Modificación del pom.xml

Igual que hicimos antes, lo primero que tenemos que hacer es modificar el fichero "pom.xml" del proyecto.
En este caso vamo a realizar muchos más cambios, para poder aprovechar las distintas características de distintos plugins,
entre las que debemos destacar (para el caso que nos ocupa) la generación automática de las clases clientes del servicio
para ser utilizadas directamente por nuestro cliente. También crearemos las dependencias necesarias, en este caso dependecias
con los distintos módulos de Rampart, para la seguridad en los servicios web.

El fichero "pom.xml" queda de la siguiente forma:


	4.0.0
	com.autentia.wss.usertoken
	WSSUserTokenClient
	jar
	1.0-SNAPSHOT
	WSSUserTokenClient
	http://maven.apache.org
	

		
			
			
				org.apache.axis2
				axis2-wsdl2code-maven-plugin
				1.4.1
				
					
						
							wsdl2code
						
					
				
				
					
						com.autentia.wss.usertoken.client
					
					
						http://localhost:8080/axis2/services/WSSUserTokenService-1.0-SNAPSHOT?wsdl
					
				
			
			
			
				org.apache.maven.plugins
				maven-jar-plugin
				
					
						
							
								com.autentia.wss.usertoken.client.EchoClient
							
							
								com.autentia.wss.usertoken.client
							
							true
							lib/
						
						
							development
							${pom.url}
						
					
				
			
			
			
				org.apache.maven.plugins
				maven-dependency-plugin
				
					
						copy-dependencies
						package
						
							copy-dependencies
						
						
							
								${project.build.directory}/lib
							
							runtime
							true
						
					
				
			
			
			
				maven-resources-plugin
				
					
						copy-resources
						validate
						
							copy-resources
						
						
							
								${project.build.directory}/modules
							
							
								
									
										modules
									
								
							
						
					
				
			
		
	
	
		
			junit
			junit
			4.4
			test
		
		
			org.apache.rampart
			rampart-core
			1.4
		
		
			org.apache.rampart
			rampart-trust
			1.4
		
		
			org.apache.rampart
			rampart-policy
			1.4
		
	
    
    

Creación de la clase cliente

Antes de nada, para poder crear nuestra clase cliente, tenemos que generar las clases clientes para acceder al servicio,
esto lo hacemos ejecutando el siguiente comando de Maven la consola, situándonos en el directorio raíz de nuestro proyecto:

mvn wsdl2code:wsdl2code

El código nos lo ha generado en el directorio "target/generated-sources/axis2/wsdl2code/src", por lo que en nuestro proyecto de
eclipse le debemos indicar que dicho directorio es un directorio de código fuente. Esto lo hacemos en las propiedades de nuestro proyecto.

Ahora ya podemos crear nuestra clase cliente "EchoClient.java" que tendrá el siguiente código:

package com.autentia.wss.usertoken.client;

import java.rmi.RemoteException;

import org.apache.axis2.AxisFault;
import org.apache.axis2.client.Options;
import org.apache.axis2.client.ServiceClient;
import org.apache.axis2.context.ConfigurationContext;
import org.apache.axis2.context.ConfigurationContextFactory;

import com.autentia.wss.usertoken.client.WSSUserTokenService10SNAPSHOTStub.Despedir;
import com.autentia.wss.usertoken.client.WSSUserTokenService10SNAPSHOTStub.DespedirResponse;
import com.autentia.wss.usertoken.client.WSSUserTokenService10SNAPSHOTStub.Saludar;
import com.autentia.wss.usertoken.client.WSSUserTokenService10SNAPSHOTStub.SaludarResponse;

/**
 * 

* EchoClient.java
Clase que prueba la invocacion a nuestro web service de * echo *

*/ public class EchoClient { private String user; private String password; public String getUser() { return user; } public void setUser(String user) { this.user = user; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public EchoClient(String user, String password){ this.setUser(user); this.setPassword(password); } /** * Metodo principal de la clase * * @param args */ public static void main(String[] args) { EchoClient client = new EchoClient("autentia","password"); try { System.out.println(client.callServiceSaludar("Borja")); System.out.println(client.callServiceDespedir("Borja")); } catch (AxisFault e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (RemoteException e) { // TODO Auto-generated catch block e.printStackTrace(); } } public String callServiceSaludar(String name) throws AxisFault,RemoteException { /* * Utilizamos el stub generado a partir del wsdl que logran establecer * la conexion con el web service proveedor. */ WSSUserTokenService10SNAPSHOTStub stub =getStub(); Saludar saludar = new Saludar(); saludar.setNombre(name); SaludarResponse result = stub.saludar(saludar); return result.get_return(); } public String callServiceDespedir(String name) throws AxisFault,RemoteException{ /* * Utilizamos el stub generado a partir del wsdl que logran establecer * la conexion con el web service proveedor. */ WSSUserTokenService10SNAPSHOTStub stub =getStub(); Despedir despedir = new Despedir(); despedir.setNombre(name); DespedirResponse result = stub.despedir(despedir); return result.get_return(); } private WSSUserTokenService10SNAPSHOTStub getStub() throws AxisFault { ConfigurationContext ctx; //el directorio que le pasamos a la configuración debe contener un directorio //con nombre "modules" donde estarán los módulos de rampart "rampart-1.4" y "rahas-1.4" ctx = ConfigurationContextFactory .createConfigurationContextFromFileSystem(".", null); //indicamos la URL de punto de entrada a nuestro servicio WSSUserTokenService10SNAPSHOTStub stub = new WSSUserTokenService10SNAPSHOTStub( ctx, "http://localhost:8080/axis2/services/WSSUserTokenService-1.0-SNAPSHOT"); ServiceClient sc = stub._getServiceClient(); //vinculamos el módulo de rampart sc.engageModule("rampart"); Options options = sc.getOptions(); //indicamos usuario y contraseña options.setUserName(this.getUser()); options.setPassword(this.getPassword()); return stub; } }

Módulos de Rampart

Como podemos ver en el código fuente de la clase, vinculamos a la petición el módulo de Rampart;
para que esto funcione correctamente, cuando creamos el contexto de Rampart le pasamos como parámetro el "path"
del directorio padre que contiene un directorio con nombre "modules" donde se encuentran los módulos "rampart-1.4" y "rahas-1.4".
En nuestro caso nos hemos creado un directorio "modules" en nuestro proyecto, donde hemos copiado dichos módulos.

Pruebas unitarias

Para comprobar que nuestro cliente funciona correctamente vamos a crearnos una clase de pruebas unitarias con JUnit para probar dicho funcionamiento.
Nuestra clase de prueba la ubicaremos dentro del directorio destinado para las pruebas "src/test/java", para que luego no se incluya en el JAR que generemos.

La clase de prueba "TestEcho.java" tiene el siguiente código:

package com.autentia.wss.usertoken.client;

import java.rmi.RemoteException;

import junit.framework.Assert;
import junit.framework.TestCase;

import org.apache.axis2.AxisFault;

/**
 * 

* TestEcho.java
Clase que prueba la invocacion a nuestro web service de * echo *

*/ public class TestEcho extends TestCase { /** * Probamos el servicio saludar */ public static void testEchoSaludar() { EchoClient client = new EchoClient("autentia", "password"); try { Assert.assertEquals("Hola Borja", client .callServiceSaludar("Borja")); } catch (AxisFault e) { Assert.fail(e.toString()); } catch (RemoteException e) { Assert.fail(e.toString()); } } /** * Probamos el servicio despedir */ public static void testEchoDespedir() { EchoClient client = new EchoClient("autentia", "password"); try { Assert.assertEquals("Adios Borja", client .callServiceDespedir("Borja")); } catch (AxisFault e) { Assert.fail(e.toString()); } catch (RemoteException e) { Assert.fail(e.toString()); } } /** * Probamos el servicio saludar con un usuario incorrecto */ public static void testEchoBadUserSaludar() { EchoClient client = new EchoClient("otro", "password"); try { client.callServiceSaludar("Borja"); } catch (AxisFault e) { // si es porque no se a podido autenticar el usuario el test es // correcto Assert .assertEquals(e.getMessage(), "The security token could not be authenticated or authorized"); } catch (RemoteException e) { Assert.fail(e.getMessage()); } } /** * Probamos el servicio despedir con un usuario incorrecto */ public static void testEchoBadUserDespedir() { EchoClient client = new EchoClient("otro", "password"); try { client.callServiceDespedir("Borja"); } catch (AxisFault e) { // si es porque no se a podido autenticar el usuario el test es // correcto Assert .assertEquals(e.getMessage(), "The security token could not be authenticated or authorized"); } catch (RemoteException e) { Assert.fail(e.getMessage()); } } }

Para ejecutar los tests que hemos programado, pulsamos sobre la clase "TestEcho.java"
con el botón derecho y seleccionamos "Run as --> JUnit Test", y podemos ver el resultado de la ejecución.

Empaquetando y ejecutando el cliente

Gracias a las pruebas unitarias, podemos asegurarnos que nuestro cliente está funcionando, por lo que podemos
generar un JAR que se ejecute directamente; esto lo hacemos ejecutando el siguiente comando de Maven
en la consola, situándonos en el directorio raíz de nuestro proyecto.

mvn package

Esto nos genera el fichero "WSSUserTokenClient-1.0-SNAPSHOT.jar" en el directorio "target" de nuestro proyecto,
y también nos ha copiado todos los módulos de Rampart en en directorio "target/modules" y los JAR's de todas las dependencias
en el directorio "target/lib". De esta forma, si abrimos una consola y vamos al directorio "target" de nuestro proyecto, podemos ejecutar
directamente nuestro cliente con el comando:

java -jar WSSUserTokenClient-1.0-SNAPSHOT.jar

Conclusiones

Como podéis ver el módulo Rampart para Axis2 nos proporciona la implementación necesaria para WS-Security, y que combinando varias tecnologías
(Axis2, Maven, Rampart, JUnit) podemos generar y probar, en este caso servicios web seguros de un modo relativamente sencillo.

Un saludo.
Borja Lázaro de Rafael.

8 COMENTARIOS

  1. Hola buenas. he seguido el tutorial pero a la hora de crear el paquete del servicio con mvn package (parate del servidor) me da BUILD ERROR: Internal error executing goal \\\’org.aache.axis2-aar-maven-plugin:1.4.1:aar\\\’ Unable to load the mojo \\\’org.apache.axis2-aar-maven-plugin:1.4.1:aar\\\’ in the plugin \\\’org.apache.axis2-aar-maven-plugin\\\’. A required class is missing: org/apache/maven/archiver/MavenArchiveConfiguration.
    Alguna sugerencia o idea, gracias de antemano.

  2. Esta muy bien y me ha ayudado mucho. Solo una puntualización al crear la clase cliente:
    \\\»mvn wsdl2code:wsdl2code\\\» no me funcionó y usé \\\»mvn axis2-wsdl2code:wsdl2code\\\» que fue perfectamente.

  3. Excelente, eso si faltan algunos detalles como indicar que el comando de generación de código es: mvn axis2-wsdl2code:wsdl2code y no mvn wsdl2code:wsdl2code y que faltan el package bouncycastle y backport-util-concurrent para los test, pero aún así aparece el error en el header, en mi caso almenos, de todas formas hay una clase (ReadWriteLock) que me esta jodiendo…. alguna idea??

  4. Gracias por los comentarios, los cambios que has necesitado hacer para que te funcionase seguramente se deben a que el tutorial lo hice hace más de dos años, y en este tiempo se han cambiado versiones y datos de configuración.
    En cualquier caso, espero que os siga sirviendo como punto de partida.

  5. No mamen, claro que funciona, solo tenia un pequeño error en la generación de las clases Java desde el WSDL pero en el comentario de bogoa666 dice cual es el comando que se debe de ejecutar en la consola del maven. Lo demás que pueda parecer error solo se soluciona con \\\»Investigación\\\» investiguen huevones!!!

DEJA UNA RESPUESTA

Por favor ingrese su comentario!

He leído y acepto la política de privacidad

Por favor ingrese su nombre aquí

Información básica acerca de la protección de datos

  • Responsable:
  • Finalidad:
  • Legitimación:
  • Destinatarios:
  • Derechos:
  • Más información: Puedes ampliar información acerca de la protección de datos en el siguiente enlace:política de privacidad