Archivos del sitio webservices

UsernameToken en Jax-ws (1/2)

Posteado por Felix G. Borrego en Junio 4th, 2010

El objetivo de este artículo es mostrar como usar UsernameToken según la especificación Web Services Security UsernameToken Profile 1.0. El artículo consiera que el lector ya tiene experiencia en Jax-ws y se centra sólo en la configuración del mecanismo de seguridad para publicar y consumir servicios que requieran la identificación del solicitante con usuario y password haciendo uso de Jax-ws.

Dependencias requeridas

Aunque las dependencias necesarias están incluidas en algunos servidores de aplicaciones, si lo deseamos podemos o bien bajar la implementación de referencia directamente desde su web:

https://jax-ws-commons.dev.java.net y https://jax-ws.dev.java.net

O definir la dependencia en caso de que el proyecto sea Maven.

Definir  un nuevo manejador JAX-WS

En primer lugar indicaremos sobre la clase que implementa el WS el handlerchain.xml que define el manejador de seguridad  mediante la anotación @HandlerChain.


@HandlerChain(file = "handlerchain.xml")
@WebService(serviceName = "IcmsWSAPIService", targetNamespace = "http://www.juntadeandalucia.es/icms", name = "IcmsWSAPIClient", portName = "IcmsWSAPIPort", endpointInterface = "es.juntadeandalucia.icms.IcmsPublisherClient")
public class IcmsPublisherWSImpl implements IcmsPublisher {..}

A continuación indicaremos en el fichero handlerchain.xml el nombre de la clase que implementa en manejador de seguridad.

Implementar un manejador para WSS

Se necesitará una clase que implemente SOAPHandler que haciendo uso de XWSSProcessor se encargue de procesar los mensajes SOAP con los header WSSE. Un ejemplo de esta clase podría ser:


/**
* Web Services Security UsernameToken Profile 1.0
*
*/
public class SecurityHandler implements SOAPHandler<SOAPMessageContext> {

XWSSProcessor securityProcessor = null;

/**
* Build the Security Handler
*/
public SecurityHandler() {

try {
// Creamos el procesador SOAP para WSS
final InputStream input = this.getClass().getResourceAsStream("/user-pass-authenticate-server.xml");
final XWSSProcessorFactory factory = XWSSProcessorFactory.newInstance();
securityProcessor = factory.createProcessorForSecurityConfiguration(input, new SecurityEnvironmentHandler());
input.close();
} catch (final Exception e) {
logger.error("No se puede inicializar El Handler", e);
}

}

/**
*  Recuperamos el bloque header.
*/
public Set<QName> getHeaders() {
final QName securityHeader = new QName("http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-   secext-1.0.xsd", "Security", "wsse");
final HashSet<QName> headers = new HashSet<QName>();
headers.add(securityHeader);
return headers;
}

public boolean handleFault(final SOAPMessageContext messageContext) {
return true;
}

public boolean handleMessage(final SOAPMessageContext messageContext) {
final SOAPMessage message = messageContext.getMessage();

try {
// Contexto WSS
final ProcessingContext context = securityProcessor.createProcessingContext(message);
context.setSOAPMessage(message);
SOAPMessage verifiedMsg = null;
verifiedMsg = securityProcessor.verifyInboundMessage(context);

// Obtenemos el usuario y pass indicado en la request
String username = null;
String pass = null;
final Subject subject = SubjectAccessor.getRequesterSubject(context);

final Set<Principal> principals = subject.getPrincipals();
if (!principals.isEmpty()) {
final Principal principal = principals.iterator().next();
// Obtenemos en CN: CN=userName
username = StringUtils.substringAfterLast(principal.getName(), "=");
}
final Set<Object> publicCredentials = subject.getPublicCredentials();
if (!publicCredentials.isEmpty()) {
pass = (String) publicCredentials.iterator().next();
}

// Verificamos el comportamiento user y pass
validateUser(username, pass);

messageContext.setMessage(verifiedMsg);
} catch (final XWSSecurityException e) {
logger.error("WSS validation problem. ", e);
throw SecurableSoapMessage.newSOAPFaultException("WS validation problem.", e);
}

return true;
}

/** Validación específica de la aplicación.
* @param username
* @param pass
* @throws XWSSecurityException
*/
private void validateUser(final String username, final String pass) throws XWSSecurityException {
// Verificación específica de la aplicación.
//throw new XWSSecurityException("Invalid user." + username);

}

public void close(final MessageContext messageContext) {
}

private static Logger logger = Logger.getLogger(SecurityHandler.class);

}

Esta clase será la encargada de procesar todos los mensajes, verificando que todos son válidos y contienen un usuario y pass correcto. Un ejemplo de un mensaje válido WSS sería:


<?xml version="1.0" encoding="UTF-8"?>
<S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/">
<S:Header>
<wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"       S:mustUnderstand="1">
<wsse:UsernameToken xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" wsu:Id="XWSSGID-1255949753848350309586">
<wsse:Username>usuario</wsse:Username>
<wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText">****</wsse:Password>
<wsse:Nonce EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary">9w+YwOatF9l1/otioQ75d5Yr</wsse:Nonce>
<wsu:Created>2009-10-19T10:55:54.418Z</wsu:Created>
</wsse:UsernameToken>
</wsse:Security>
</S:Header>
<S:Body>
<ns2:ping xmlns:ns2="http://www.juntadeandalucia.es/icms" xmlns:ns3="http://www.juntadeandalucia.es/icms/data">
<ns2:request>test</ns2:request>
</ns2:ping>
</S:Body>
</S:Envelope>

Próximamente cómo crear un cliente Jax-ws que consuma servicios securizados con   UsernameToken.

Tips: Como crear una instancia de un proxy JAX-WS dinámicamente.

Posteado por Felix G. Borrego en Junio 16th, 2008

Con JAX-WS 2.x, al igual que era posible con Xfire, podemos crear dinámicamente un proxy de un servicio web sin necesidad de recurrir a tools (wsimport) si disponemos de la interfaz Java del servicio web (SEI) anotada correctamente.

Ej:
@WebService(name=”ServiceWSPort”, targetNamespace=”http://www.xnoccio.com/serviceWS”)
public interface ServiceWS {
@WebMethod
public String ping(Strin hola);

}

Para poder invocar este Servicio Web, lo normal sería generar las clase cliente proxy desde el WSDL, pero un mecanismo mas sencillo es simplemente hacer uso de las capacidades dinámicas de JAX-WS para generar en caliente una implementación cliente de esta interfaz:

Ej:
URL wsdlURL=new URL(“http://hostname:8080/path/ServiceWS?wsdl”);
Qname serviceQname=new Qname(“http://xnoccio.com/serviceWS”,”ServiceWSService);
Qname postQname=new Qname(“http://xnoccio.com/serviceWS”,”ServiceWSPort);
Service service=Service.create(wsdlURL,serviceQname);
ServiceWS clienteProxyWS=(ServiceWS) service.getPort(portQname,ServiceWS.class);
clienteProxyWS.ping(“hola mundo”);

Webservice-client con Metro JAX-WS y Autenticación Básica

Posteado por Manuel Navarro Almuedo en Junio 7th, 2008

En esta ocasión os voy a comentar cómo generar un cliente de un webService con autenticación básica (usuario/contraseña , Basic Authentication) usando el plugin para maven JAX-WS.
Doy por hecho que todos sabeis cómo integrar este plugin con maven, podeis obtener toda la información necesaria sobre este plugin en su página oficial.

Al final tendremos algo parecido a esto, introduciendo una nueva entrada en la lista de plugins:

<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>jaxws-maven-plugin</artifactId>
<executions>
<execution>
<phase>process-sources</phase>
<goals>
<goal>wsimport</goal>
</goals>
</execution>
</executions>
<configuration>
……
</configuration>
<dependencies>
… lista de dependencias del plugin …
</dependencies>
</plugin>

Para el caso que nos ocupa, vamos a suponer que tenemos un WSDL (Web Services Description Language) desplegado en un servidor que requiere Usuario y Contraseña, ¿cómo podemos indicarle a este plugin la URL al fichero descriptor del servicio web? ¿cómo le indicamos el usuario y contraseña?
Pues todo esto se hace en la sección “configuration“.
Tenemos mucha documentación disponible la web relacionada con wsimport.

Un sabio amigo mío se quejaba una vez en clase de programación, diciendo que la programación era como empanar filetes, y que no le podían enseñar a hacer filetes empanados si antes no se había comido ninguno. Pues bien, siguiendo esta corriente filosófica os pongo un ejemplo y os comento después las secciones que nos afectan:
<configuration>
<wsdlUrls>
<wsdlUrl>[http/https]://[servidor1]:[puerto1]/[rutaAlDescriptor1]?wsdl</wsdlUrl>
<wsdlUrl>[http/https]://[servidor2]:[puerto2]/[rutaAlDescriptor2]?wsdl</wsdlUrl>
</wsdlUrls>
<packageName>mipaquete.ws.client</packageName>
<sourceDestDir>${basedir}/src/main/java</sourceDestDir>
<verbose>true</verbose>
<extension>true</extension>
<xadditionalHeaders>true</xadditionalHeaders>
<xdebug>true</xdebug>
<xauthFile>${basedir}/src/site/resources/auth.conf</xauthFile>
</configuration>

En primer lugar definimos una lista de URL a los distintos ficheros descriptores y posteriormente el paquete en el que generarán las clases consumidoras del servicio web.
Otro parámetro importante en el caso que nos ocupa es “xauthFile”. Este parámetro le indica al plugin una ruta a un fichero, en dicho fichero se almacena el Usuario/Contraseña de acceso a la lista de wsld’s anterior.

Este fichero debe contener una serie de lineas de la siguiente manera:
[http/https]://[usuario]:[contraseña]@[servidor]:[puerto]/[rutaAlDescriptor]?wsdl

Tantas líneas como se requieran según las URL’s indicadas en “wsdlUrls”.

Pues bien, si generamos a continuación las clases, nuestro plugin le pedirá a los servidores los distintos wslds’s y se autenticará correctamente usando el fichero auth.conf.

¿Hemos acabado ya?
Pues hemos hecho la mitad, por ahora lo que tenemos es una serie de clases que pueden consumir los distintos servicios web.

Pregunta, ¿Se volverá a requerir Usuario/Contraseña cuando se invoque a unos de estos métodos clientes del servicio web?
Pues sí.

¿Y se supone que ese usuario y contraseña lo coge del fichero de autenticación anterior?
Pues no, este fichero sólo nos ha servido para generar las clases clientes, nada más.

Entonces, un segundo paso es introducir un sistema de autenticación dentro de nuestro cliente, ¡vamos a ello!

Una de las posibles opciones es generar una clase de autenticación, que extienda de “java.net.Authenticator”.
Su implementación es bastante simple, os pongo un ejemplo:


import java.net.Authenticator;
import java.net.PasswordAuthentication;

public class MiAutenticador extends Authenticator {
static final String user = “miNombreDeUsuario”; // Login
static final String pass = “miContraseña”; // Password

public PasswordAuthentication getPasswordAuthentication() {
return (new PasswordAuthentication(user, pass.toCharArray()));
}
}

Creo que no merece más comentarios, sólo indicar que los atributos “user” y “pass” deberían leerse de un fichero de propiedades por ejemplo, pero de ninguna manera deberían estar a fuego en nuestro código.

Ahora que tenemos nuestra clase de autenticación ¿cómo “conectarla” en nuestras clases clientes?
Pues bien, lo que debemos hacer es indicar que deseamos usar este “Autenticador” justo antes de la llamada al
webService que requiere dicha autenticación, algo como:

........
Authenticator.setDefault(new MiAutenticador());
API_webService.metodo(params);
........

Esto se puede implementar de manera elegante usando el patrón de Diseño Decorador (Decorator Pattern ), de manera que por defecto se “decore” los distintos métodos del api webService con nuestro autenticador.

Pues nada más, espero haber sido claro.

Gracias, namasté y buena suerte.

Preprocesado de peticiones SOAP en JAX-WS

Posteado por Felix G. Borrego en Mayo 25th, 2008

En el caso de que necesitemos establecer algún tipo de preprocesamiento/filtro/mecanismo de seguridad a un servicio web, una de las formas más interesantes es utilizando los manejadores HandlerChain que proporciona JAX-WS 2.x.

En primer lugar implementamos el manejador, que nos permitirá realizar procesamientos y postprocesamientos sobre las peticiones SOAP que lleguen a nuestro servicios web. Os dejo un ejemplo de un manejador que permite filtrar por ip:



public class SecurityServiceWebHandler  implements MessageHandler{

 private static Log log=LogFactory.getLog(SecurityServiceWebHandler.class);

 public Set getHeaders() {

 	return null;

 }

 public void close(MessageContext context) {

 }

 public boolean handleFault(MessageHandlerContext context) {

 	return true;

 }	/** Comprueba que las ips que acceden a la aplicación son efectivamente ip permitidas.

  * @see javax.xml.ws.handler.Handler#handleMessage(javax.xml.ws.handler.MessageContext)

  */

 public boolean handleMessage(MessageHandlerContext context) {

 	ServletRequest servletRequest = ((ServletRequest)context.get(MessageContext.SERVLET_REQUEST));

                // Obtenemos la ip remota que invoca al Servicio Web

 	String remoteAddres=servletRequest.getRemoteAddr();

 	String allowed="192.168.10.160,80.58.0.12";

 	if(StringUtils.contains(allowed, remoteAddres)){

 		if(log.isInfoEnabled())log.info("Servicio Web solicitado desde ip: "+remoteAddres);

 	}else{

 		log.error("Acceso denegado. La ip "+remoteAddres+" no tiene permiso para acceder a los WS.");

 		throw new WebServiceException("Acceso denegado. La ip "+remoteAddres+" no tiene permiso para acceder a los WS.");

 	}

 	return true;

 }

}

Una vez implementado el manejador, lo siguiente es publicarlo en el fichero handlerchain.xml

<handler-chains xmlns=”http://java.sun.com/xml/ns/javaee”>
<handler-chain>
<handler>
<handler-class>org.viafirma.conector.security.SecurityServiceWebHandler</handler-class>
</handler>
</handler-chain>
</handler-chains>
El último paso es asociar nuestro servicio web con el manejador, mediante la anotación @HandlerChain.



@HandlerChain(file="handlerchain.xml")

@WebService(serviceName="ConectorFirmaRMIService",targetNamespace = "http://viafirma.org/client/", name = "ConectorFirmaRMIClient",portName="ConectorFirmaRMI",

endpointInterface = "org.viafirma.cliente.firma.rmi.FirmaClienteRMI")

public class ConectorFirmaRMI  extends UnicastRemoteObject implements FirmaClienteRMI {

Como ejemplo, gracias a este código podremos recuperar y registrar las ips de todas las peticiones entrantes a nuestro servicio web, y denegar las ips no admitidas.

SOAP Web services vs RMI

Posteado por Felix G. Borrego en Noviembre 30th, 2007

Hace tiempo que abandone el “Mantra Web services para todo“, y me horrorizo al escuchar a los que piensan que RMI es cosa del pasado, aunque reconozco que a menudo es fácil tomar la decisión y elegir SOAP, en ocasiones la elección no es tan clara.

Dejo aquí mis criterios para elegir:

- Si algunas de las aplicaciones que se van a integrar no son Java, evidentemente la opción RMI se elimina, ya que salvo casos excepcionales, si soy de los que piensan que RMI/IIOP es cosa del pasado.

- Si el rendimiento, la eficiencia y los tiempos de respuesta son una preocupación, a falta de tener en cuenta otros criterios, RMI es la mejor opción. A modo de ejemplo, nuestra plataforma de Formularios ( Formul@) ofrece ambas implementaciones, y la implementación RMI es un orden de magnitud mas rápida que su homóloga SOAP. En en un bucle de 1000 iteraciones, el cliente basado en web services tardo en promedio un total de 10186ms en renderizar los formularios, mientras que la versión basada en RMI tardo de media 946ms.

- Si ambas aplicaciones son Java y ademas se ejecutan en la misma máquina o subred, la opción RMI es sin duda mi preferida.

- Si se pretende construir o evolucionar en el futuro a un ambiente SOA, aunque SOA no descarta RMI (la mayoría de los ESBs lo contemplan), la mejor opción es implementar un servicio basado en Web Services.

- Si las aplicaciones son desarrolladas por diferentes proveedores, la opción sería Web Services, ya que el desarrollo “primero contrato” se hace imprescindible.

- En otro caso, utilizo mi moneda de la suerte para decidir :) , aunque la moneda suele tiene en ambas caras “Web Service”.