Introducción a los Servicios Web en Java

En esta página veremos una presentación sobre la seguridad y los servicios web. Empezaremos revisando algunos conceptos básicos, luego veremos algunas de las técnicas de seguridad más comunes como SSL (Secure Socket Layer). Después examinaremos el proceso implicado en la autentificación y autorización, así como la privacidad de datos.

. Introducción a la Seguridad en los Servicios Web

Empezaremos con una breve introducción de los conceptos y arquitecturas que hay bajo la seguridad de un Servicio Web.

. Criptografía Asimétrica -- Claves Privada y Pública

Durante la Segunda Guerra Mundial, la marina de los EEUU encontró que una de las mejores formas de encriptar las transmisiones de radio era hablar en el idioma de los Indios Navajos, un idioma al que no se le conocia ninguna conexión lingüitisca con ningún otro idioma. Nunca se pudo romper esta técnica rudimentaria, y se crearon libros de código más complejos y mecanismos computacionales.

La criptografía moderna ha avanzado considerablemente desde entonces. Para los propósitos de esta explicación, hay dos tipos básicos de algoritmos de criptografía: simétrico (o convencional) y asimétrico (también conocido como criptografía de clave pública-privada). Para la encriptación simétrica, la clave (o código) que se usa para encriptar el mensaje es el mismo que se usa para desencriptarlo. Para la encriptación asimétrica, se utilizan dos claves diferentes para bloquear y desbloquear (encriptar y desencriptar) los mensajes y ficheros. Las dos clase se enlazan matemáticamente pero la derivación de una de la otra no es factible matemáticamente hablando. Una clave pública de un individuo se distribuye a otros usuarios y estos la utilizan para encriptar mensajes para ese individuo. El individuo mantiene en secreto la clave privada y la usa para desencriptar los mensajes enviados con la clave pública.

Desde el punto de vista computacional, pasar mensajes usando la encriptación simétrica es más eficiente y consume menos recursos que el método asimétrico. La asimetría tiene sus ventajas cuando se trabaja en grandes comunidades porque se puede distribuir libremente la clave 'pública'.

. Identidades

La identidad es la piedra angular que permite que los servicios web funcionen juntos. Hoy en día la mayoría de los servicios web trabajan en solitario, pero todo el mundo habla de ensamblar Servicios Web para crear servicios de negocios más complejos y poderosos. El ensamblaje de servicios web requiere que los servicios compartan información, particularmente una vez que hemos empezado a añadir seguridad a nuestros servicios.

Consideremos como implementan la seguridad la mayoria de los Servicios Web de hoy en día. Cada negocio que ofrece un Servicio Web seguro mantiene una lista de usuarios autorizados, y cada usuario se autentifica a sí mismo usando un reto userid/password. Esto no tiene sentido para servicios web complejos, y está es una de las razones por las que hemos oido como grandes compañías como Microsoft anuncian que Passport soportará autentificación federada usando Kerberos V5.0, y Sun lanza el proyecto Liberty Alliance.

Las identidades se usaban durante la autentificación entre puntos de comunicación, permitiendo la identificación y por lo tanto la autorización, auditoría, etc. La aproximación más común hoy en día es que cada identidad de seguridad tiene un nombre, una clave privada y un certificado X.509. Este certificado contiene características públicas (credenciales aka) de una identidad particular, incluyendo la clave pública.

. La Firma de Datos

La firma de datos es sólo proporcionar el origen de los datos. Si uno encripta los datos usando la clave privada entonces cualquiera que pueda descriptarlos correctamente puede fácilmente acceder al origen de los datos: una desencriptación correcta significa que los datos fueron encriptados con la correspondiente clave privada. Como la clave privada siempre se mantiene privada y nunca abandona la posesión de la persona que la generó, la correcta desencriptación de los datos protege su origen.

Desafortunadamente, los algoritmos asimétricos son bastante lentos, por eso se usan funciones especiales (como MD5 o SHA). Estas funciones hash especiales primero calculan la huella de los datos (por ejemplo, 16 bytes para MD5 y 20 bytes para SHA) que luego es firmada. La parte receptora calcula la huella usando la misma función, desencripta la huella firmada usando la clave pública, y finalmente compara las huellas calculadas y la desencriptada. Si ambas huellas son iguales entonces se ha verificado el origen de los datos.

. Certificados Verdaderos

Veamos como se crean los certificados. Primero se genera la pareja de claves (pública y privada). Luego, se crea un Certificate Signing Request (CSR). Un CSR simplemente es una colección de datos que contiene toda la información que se incluirá en el certificado (incluyendo la clave pública) y que se firma con la clave privada generada. Luego, el CSR se envía a una Autoridad de Certificación que crea el certificado y lo firma con su clave privada. Firmando el certificado la Autoridad de Certificación verifica que el certificado contiene datos válidos. Cualquiera que crea en el Autoridad de Certificación puede usar su certificado (más precisamente su clave pública) y verificar el certificado firmado. El echo de que creamos en algunas identidades se expresa almacenando sus certificados en un almacen especial. La verificación del certificado se puede extender más niveles, un certificado puede estar firmado por alguna identidad que está firmada por otra entidad, etc. Si alguno de los certificados de la cadena se considera como verdadero esto significa que todos los demás certificados de la cadena también son verdaderos.

. Los Servicios Web y los Mecanismos del API Secure

Las tecnologías de seguridad tradicionales no son suficientes para los Servicios Web. El principal problema es su dependencia del transporte. Por ejemplo, la tecnología de seguridad más usada, SSL (Secure Socket Layer), está atada a un punto final de la comunicación de red. Más precisamente, la identidad sólo se puede asignar con el punto de la comunicación que normalmente comparten muchos Servicios Web.

Otras tecnologías de seguridad, como GSS-API (Generic Security Service Application Programmers Interface) y mecanismos de seguridad basadas en ésta como SPKM (Simple Public Key Mechanism) y Kerberos, están diseñados especificamente para usarlos en arquitecturas de acoplamiento débil. GSS-API es independiente tanto del transporte como de los mecanismos de seguridad (independencia de los mecanismos de seguridad significa que las tecnologías subyacentes para la criptografía, representación de identidad y firma de datos están totalmente encapsuladas). El mecanismo de seguridad en Servicios Web sigue siendo SSL pero se está incrementando el uso de SPKM y Kerberos. SPKM está basado en elementos de criptografía asimétrica explicados más arriba, por eso es ideal para entornos de Servicios Web altamente dispersos; Kerberos usa criptografía simétrica.

. Seguridad en Acción: Autentificación, Autorización y Privacidad de los Datos

La autentificación, la autorización y la privacidad de los datos son los tres elementos principales de una arquitectura de seguridad. Veamos un sencillo ejemplo que muestra estos conceptos. Empezará con una simple implementación de una funcionalidad de cuenta bancaria. Echa un vistazo al código de la clase AccountImpl.java:

/*
 * AccountImpl.java
 *
 * Created on December 13, 2001, 9:25 AM
 */

package com.systinet.demos.bank;

// imports of WASP security
import org.idoox.security.AuthResult;
import org.idoox.security.Credentials;
import org.idoox.security.PrincipalAuthenticator;
import org.idoox.security.server.Current;

import org.idoox.webservice.server.Initializable;
import org.idoox.webservice.server.WebServiceContext;

/**
 * Account implementation
 */
public class AccountImpl
    implements Account, Initializable {
    private double balance = 0;
    private String number = "";

    public AccountImpl()  {
        this.number = ""+System.currentTimeMillis();
    }

    public void init(WebServiceContext context) {
        authenticate();
    }

    public void destroy() {
        // do nothing here
    }

    /**
     * Deposits to the account
     * @param amount amount of many to deposit
     * @throws AuthenticationException if authentication fails
     */
    synchronized public void deposit(double amount)
        throws AuthenticationException {
        checkAuth();
        this.balance += amount;
    }

    /**
     * Withdraw from the account
     * @param amount amount to withdraw
     * @throws UnsufficientFundsException thrown if account doesn't hava enough funds
     * @throws AuthenticationException if authentication fails
     */
    synchronized public void withdraw(double amount)
        throws UnsufficientFundsException, AuthenticationException {
        checkAuth();
        if(amount < this.balance) {
            this.balance = this.balance - amount;
        }
        else {
            throw new UnsufficientFundsException("The withdrawal of " + amount +
             " was requested but the balance is only " +
             this.balance+" .");
        }
    }

    /**
     * Returns the account balance
     * @return the actual balance of the account
     * @throws AuthenticationException if authentication fails
     */
    synchronized public double getBalance()
        throws AuthenticationException  {
        checkAuth();
        return this.balance;
    }

    /**
     * Sets the account balance
     * @param amount the actual balance of the account
     * @throws AuthenticationException if authentication fails
     */
    synchronized public void setBalance(double amount)
        throws AuthenticationException {
        checkAuth();
        this.balance = amount;
    }

    /**
     * Returns the account number
     * @return account number
     * @throws AuthenticationException if authentication fails
     */
    public String getAccountNumber()
        throws AuthenticationException {
        checkAuth();
        return this.number;
    }

    /**
     * Sets the account number
     * @param accountNumber account number
     * @throws AuthenticationException if authentication fails
     */
    public void setAccountNumber(String accountNumber)
        throws AuthenticationException {
        checkAuth();
        this.number = accountNumber;
    }

    /**
     * Close the account
     * @throws AuthenticationException if authentication fails
     */
    public void close()
        throws AuthenticationException {
        checkAuth();
        org.idoox.webservice.server.WebServiceContext context =
            org.idoox.webservice.server.WebServiceContext.getInstance();
        org.idoox.webservice.server.LifeCycleService lc = 
            context.getLifeCycleService();
        lc.disposeServiceInstance(this);
    }

    /**
     * Creates and sets the security identity credentials if they are
     * not alread set
     */
    private synchronized void authenticate()  {
        Current current = Current.getInstance();
        if (current.getCredentials() == null) {
            PrincipalAuthenticator auth = current.getAuthenticator();
            AuthResult result = auth.authenticate("bank-server", "password".getBytes());
            if (result.resultCode != AuthResult.AUTH_STATUS_SUCCESS) {
                System.err.println("Unable to authenticate");
            }
            current.setCredentials(new Credentials[] { result.creds });
        }
    }

    /**
     * Performs very simple authorization based on the hardcoded
     * identity name, which is able to manipulate the account.
     *
     * @throws AuthenticationException if the client is not authorized
     */
    private void checkAuth()
        throws AuthenticationException  {
        Current current = Current.getInstance();
        Credentials credentials = current.getReceivedCredentials();
        if(credentials != null) {
            String caller = credentials.getName();
            if(caller == null || !caller.equals("john")) {
                throw new AuthenticationException("Access denied.");
            }
        }
    }
}

Todo el código relacionado con la seguridad está en los dos métodos que hay al final de la clase authenticate y checkAuth. El método authenticate abre un almacen de certificados local y recupera las credenciales públicas protegidas por password (representadas por la clave privada de la identidad y el certificado X.509) que posteriormente se utilizan para la autentificación del servicio. El código del cliente es casi idéntico. Chequea el código de la clase BankClient.java que realiza la autentificación al prinicipio de su método main:

/*
 * This is a WASP client.
 * BankClient.java
 * Created on December 13, 2001, 10:41 AM
 */
package com.systinet.demos.bank;

import org.idoox.webservice.client.WebServiceLookup;
import org.idoox.wasp.Context;

// imports of WASP security
import org.idoox.security.AuthResult;
import org.idoox.security.Credentials;
import org.idoox.security.PrincipalAuthenticator;
import org.idoox.security.client.Current;

/**
 * Bank client application
 */
public class BankClient extends Object {
    /**
     * Runs the demo on the client-side
     * @param args the command line arguments
     */
    public static void main (String args[])
        throws Exception {
        String username = args[0];
        String password = args[1];

        if(username == null)
          username = "john";

        if(password == null)
          password = "password";

        System.out.println("Authenticating user "+username);
        // init the lookup
        WebServiceLookup lookup = (WebServiceLookup)
            Context.getInstance(Context.WEBSERVICE_LOOKUP);

        // obtain and set the crededentials
        Current current = Current.getInstance();
        PrincipalAuthenticator auth = current.getAuthenticator();
        AuthResult result = auth.authenticate(username, password.getBytes());
        if (result.resultCode != AuthResult.AUTH_STATUS_SUCCESS) {
            System.err.println("Unable to authenticate");
            return;
        }
        current.setCredentials(new Credentials[] { result.creds });

        // get the proxy to the Web Service from the lookup
        Account account = (Account)
            lookup.lookup("http://localhost:6060/AccountImpl/", Account.class);
        // now, call the methods on your Web Service interface
        System.out.println("Account #" + account.getAccountNumber() + " created.");
        System.out.println("Putting $10 000 on account #" +
         account.getAccountNumber() + " .");
        account.deposit(10000);
        System.out.println("Getting $7 000 from account #" +
         account.getAccountNumber() + " .");
        account.withdraw(7000);
        System.out.println("Account #" + account.getAccountNumber() + " balance is "
         + account.getBalance());
        account.close();
    }
}

Después de que el código del cliente llame al servicio, se realiza la autentificación mútua. Básicamente, se intercambia las credenciales públicas (certificados), y si la parte remota cree en el certificado del otro lado tiene lugar la comunicación.

checkAuth realiza la autorización básica. Primero, se recuperan las credenciales del llamante de la llamada entrante. En nuestro ejemplo, se chequea el nombre del cliente, y si es igual a 'john', la autorización tiene éxito. De otro modo, la autorización falla. Dicha validación dentro del código es la más simple posible, pero a menos que todos los clientes de nuestro banco se llamen 'john', sólo es viable para propósitos de demostración. en el mundo real de Johns, Jacks y Janes, deberíamos utilizar las tecnologías como JAAS (Java Authentication and Authorization Service) para propagar la identidad del lado del llamante en el código Java, y realizar chequeos de autorización (normalmente en un fichero de política). La aproximación JAAS permite una mejor integración de la seguridad de los Servicios Web con los Servidores de aplicaciones J2EE que ya usan esta tecnología para propósitos de autentificación y autorización.

Para la usar la integración JAAS del API de Seguridad WASP, el interface Credentials tiene el método getSubject() que obtiene el ejemplar de JASS javax.security.auth.Subject.

La privacidad es la última característica (pero no la menos importante) de la arquitectura de seguridad de los Servicios Web. La privacidad de los datos asegura que los datos recibidos no se han modificado en el transporte, y también tiene cuidado de la confidencialidad de los datos. Normalmente, los datos se encriptan usando algún cifrado simétrico (como Triple DES o RC5) porque son mucho más rápidos que los asimétricos. Generalmente, las cifrados asimétricos sólo se usan para un intercambio seguro de claves simétricas. Si miras los mensajes intercambiados en nuestro ejemplo, deberías ver una comunicación totalmente encriptada. En algunos casos, especialmente en escenarios de enrutados complejos de mensajes, tiene sentido encriptar sólo las partes específicas del mensaje para dejar que los intermediarios de la comunicación accedan a las cabeceras. De forma similar, también se puede realizar la firma de datos, sólo en los elementos de mensaje que lo requieran.

. Instrucciones para Ejecutar el Ejemplo

Descarga el archivo con el código fuente y descomprimelo en el directorio c:\wasp_demo. Todo las clases del ejemplo residen en el paquete com.systinet.demos.bank y podrás localizar todos los scripts en el subdirectorio bin.

PASOS ADICIONALES DE INSTALACIÓN:
Necesitarás instalar algunos añadidos de seguridad para los ejemplos. Es absolutamente necesario que sigas los pasos de instalación de JCE y JASS de la página security installation document de WASP. También puedes encontrar este documento en la guía de tu instalación local de WASP en (doc/htmldata/installation_guide_body.html#security_install).

También necesitarás ejecutar el script install-security.bat situado en el subdirectorio bin de tu instalación de WASP. Este script es interactivo. Por favor, especifica localhost como tu nombre de host y acepta todos los demás valores por defecto.

Finalmente, necesitas modificar el script env.bat situado en el directorio c:\wasp_demo\bin. Por favor, corrige los valores de las variables de entorno WASP_HOME y WASP_DEMO. Para hacer esto necesitarás realizar las instalación de seguridad de JCE, JAAS y WASP mencionadas arriba. Luego arranca el entorno de servicios web con el script serverstart del directorio WASP_HOME\bin. El siguiente paso es la generación de todas las identidades usadas en la demo (habrás observado que en el código se utilizan las identidades bank-server y john). Estas identidades se generan en los almacenamientos seguros apropiados. También, y como esta demo no usa ninguna autoridad de certificación, ambas identidades se insertan en los almacenes creibles del otro lado de la comunicación. Esta es la forma más simple de hacer que estás entidades sean creídas. Luego, se crea la identidad jack del lado del cliente. Esta identidad sólo se crea en el lado del cliente, por lo que no es buena en el lado del servidor. Por favor, ejecuta el script createAccounts que crea automáticamente todas las identidades requeridas. Finalmente, podemos usar los scripts deploy y run para desplegar y ejecutar la demos. Observa que necesitarás especificar un nombre de usuario y una password para el administrador de WASP, luego despliegua la demo en el entorno de ejecución (nuestra demo usa los valores por defecto). La aplicación cliente debería autentificar perfectamente al usuario john, pero debería fallar con el usuario jack.

NOTA:
Podrías haber observado que el servicio web Account tiene estado: recuerda el número de la cuenta y el estado del balance entre llamadas sucesivas. Si ejecutarámos dos aplicaciones cliente en paralelo, se crearían dos ejemplares separados de Account.

El último paso es eliminar la demo usando el script undeploy (de nuevo necesitarás especificar el nombre y password del adminstrador de WASP.

NOTA:
En las siguientes páginas del tutorial no vamos a utilizar seguridad, por eso necesitamos poner el servidor WASP en modo no seguro ejecutando el script unsecure. Por favor, para el servidor WASP antes de hacer esto, de otro modo se perderían los cambios hechos en los ficheros de configuración.

COMPARTE ESTE ARTÍCULO

ENVIAR A UN AMIGO
COMPARTIR EN FACEBOOK
COMPARTIR EN TWITTER
COMPARTIR EN GOOGLE +
ARTÍCULO ANTERIOR

SIGUIENTE ARTÍCULO

¡SÉ EL PRIMERO EN COMENTAR!
Conéctate o Regístrate para dejar tu comentario.