Archivo

Archivo del autor

Consejos para hacer una exposición o presentación en público

domingo, 11 marzo 2012, 3:33 3 comentarios

Leyendo una entrada en el blog de Inc.com he recordado ese gran libro que es Cómo hablar en público e influir en los hombres de negocios de Dale Carnegie. Sí, el título del libro puede ser un poco rimbombante y presuntuoso; pero cuando lo lees, descubres un libro muy práctico y directo, con pequeñas píldoras de cómo perder el miedo a hablar en público y hacer exposiciones efectivas. Es un libro muy bien estructurado, con un capítulo para cada consejo y lleno de ejemplos y anécdotas que amenizan la lectura. Lo recomiendo.

La entrada de hoy pretende ser un pequeño resumen a modo de consejos muy concisos que he encontrado en este libro y en otras fuentes de cómo hablar en público:

  • No improvises, prepárate la exposición. Incluso en aquellos casos en los que el discurso es improvisado, procura haberte preparado recursos para salir al paso y practica estas situaciones de antemano.
  • Repasa expresión corporal y practica tu voz delante de un espejo.
  • Habla de temas que conozcas, pero sobre todo, de los que estés convencido o muy involucrado. Más importante que tener conocimientos sobre el tema, es tener experiencia que transmitir y ganas de transmitirla. El entusiasmo que reflejas es muy importante para conectar con el público. Intenta infundir en el auditorio los mismos sentimientos que en ti infunde lo que estás contando.
  • Define el alcance de lo que vayas a tratar de antemano. Concreta límites, tiempo y profundidad de desarrollo. No te extiendas demasiado. No verás nunca a nadie quejarse de que «la exposición fue muy corta» :-).
  • Intenta contar una historia a lo largo de toda la exposición, para dar una linealidad a todo tu discurso. Básate en ejemplos y experiencias propias; al auditorio le interesa mucho más cuáles son tus experiencias personales con el asunto en cuestión, que la enumeración de características, fórmulas o hechos. Introduce ejemplos gráficos y representativos. Ordena y enumera tus ideas.
  • Conecta con el auditorio:
    • Comienza con algo impactante. Un dato o una anécdota que despierte el interés de forma inmediata.
    • Demuestra aprecio y respeto por el auditorio. Identifícate con él. Sé humilde pero sincero.
    • Si no eres bueno contando chistes, no lo hagas. Si lo eres, no abuses ;-P
    • Sé elegante, no digas lo bueno que eres. Si tienes que compararte con otros, hazlo mediante estudios, comparativas o benchmarks de fuentes independientes. NUNCA hables mal de otros, solo te deja en mal lugar a ti.
    • Utiliza los términos apropiados para el auditorio al que te diriges. Mastica y digiere la información para que solo le llegue aquella que le interesa o que le es relevante.
    • Si quieres convencer al auditorio de algo, no busques la confrontación; se pondrá a la defensiva y no podrás llegar a él. Intenta ganarte al auditorio, convencerle de que estás de su parte; y explica el matiz o el siguiente paso que para su opinión significa tu idea. Si quieres provocar el cambio, será mucho más fácil la metamorfosis a la conversión.
    • Haz participar al auditorio. Sin miedo. Crea debate y opinión. Convierte su idea, en la idea del grupo en su conjunto; así la aceptará con más facilidad.

Como es normal, una vez enumerados parecen obvios. Pero no está de más tenerlos todos en una lista y repasarlos de vez en cuando. Lo dicho, un libro muy recomendable.

Un saludo.

Licencia Creative Commons
Esta entrada de Juan Francisco Adame Lorite está bajo una licencia Creative Commons Atribución-CompartirIgual 3.0 Unported.

Categorías: Otros Etiquetas:

Cross-site Request Forgery (CSRF o XSRF)

miércoles, 15 febrero 2012, 0:27 Deja un comentario

El ataque CSRF es un tipo de ataque Web que aprovecha, bien una vulnerabilidad en un sitio Web (de tipo XSS) o bien un error humano mediante ingeniería social, para invocar una acción en una tercera Web en nombre del usuario sin su consentimiento.

Es también llamado XSRF (por aquello de que la equis es comúnmente la abreviatura de cross), one-click attack o session riding. En castellano, según la entrada en Wikipedia, se puede traducir por falsificación de petición en sitios cruzados, enlace hostil, ataque de un click, cabalgamiento de sesión o ataque automático.

En este ataque el usuario de manera voluntaria o no invoca una petición HTTP GET a un tercer dominio. Esta petición realiza algún tipo de acción o proceso que beneficia al atacante, utilizando las credenciales del usuario atacado que tiene en ese momento una sesión abierta en el dominio invocado.

Por lo general, este ataque aprovecha el hecho de que la sesión autenticada y autorizada del usuario se identifica mediante una cookie que contiene el identificador de sesión del sitio. Esta cookie se envía, por el navegador, de manera automática siempre que se invoque el dominio al que pertenece. De esta manera, si la sesión no ha sido cerrada por el usuario o si no ha caducado, la petición HTTP se invoca con dicha cookie, en la sesión que identifica y con los permisos y autorizaciones que el usuario tenga conferidos.

Luego el ataque tiene que hacerse desde una sesión del navegador Web abierta por el usuario:

  • Mediante XSS (Cross-site Scripting) insertando un script que haga la petición HTTP GET al dominio a atacar cuando el usaurio acceda a un tercer dominio donde se ha insertado el script. Antes de que se sanitizase (eliminar los caracteres y cadenas de texto que facilitan que los scripts se interpreten como tales) todo el contenido que se posteaba en foros o wikis, era posible insertar el script en una entrada dentro del tema en cuestión. Cuando un usuario accedía a dicho tema, se cargaba la entrada con el script que invocaba al dominio atacado. Hoy en día los foros sanitizan de las entradas de los usuarios, aunque es posible seguir encontrando vulnerabilidades complejas que permitan el XSS.
  • Mediante la inserción, también en wikis, foros o correos, de una imagen cuya URL de referencia (atributo src) es la petición HTTP a invocar. Cuando el usuario atacado cargue la página, el navegador cargará la imagen de manera automática invocando dicha petición. No se mostrará ninguna imagen, puesto que el contenido devuelto no es una imagen, pero el daño estará hecho. Para evitar este vector de ataque, los lectores de correo no permiten la carga automática de imágenes y foros y wikis solo permiten imágenes de su dominio y siguiendo un path establecido.
  • Mediante el envío de la URL de la petición HTTP en un documento o correo y, utilizando ingeniería social, consiguiendo que el usuario lo invoque.
  • Si un troyano hiciese uso de la sesión del usuario para invocar operaciones en el sitio; no sería estrictamente CSRF, pero tendría un funcionamiento muy parecido.

En todos estos casos, el navegador invoca la URL mediante una petición HTTP y remite la cookie de usuario que está asociada a dicho dominio. Esa cookie contiene el identificador de sesión que autentica al usuario y lo autoriza para realizar dicha acción en su nombre.

Esta petición tiene que tener algún tipo de efecto lateral o secundario, tiene que hacer algún tipo de transacción (mover fondos, cambiar la contraseña del usuario, enviar un mail o postear un mensaje en su nombre…). Si no no tendría ningún efecto, ni siquiera la divulgación de información confidencial; ya que el ataque es ciego y el atacante no puede recuperar la información que ha devuelto la petición al ser invocada por el usuario desde su navegador.

En el punto 9.1.1 del RFC 2616 que define el estándar HTTP en su versión 1.1, se indica que los métodos HTTP GET y HEAD no deben efectuar acción ninguna salvo el mostrar información. Se podría pensar, que el «culpable» de este ataque es el no seguir esta recomendación y permitir efectos secundarios en las peticiones GET. Pero aunque sea adecuado y mitigue algunos vectores de ataque (atributo src en imágenes); el seguir dicha recomendación, como se indica en OWASP, no elimina la vulnerabilidad. Aún puede aprovecharse mediante la invocación de métodos POST, en formularios enviados por correo al usuario o con scripts insertados mediante XSS por ejemplo, con los mismos mecanismos y efectos que el método GET.

Tampoco sirve de nada el que la petición sea HTTPS en lugar de HTTP. El ataque es idéntico.

Es imprescindible que el usuario esté autenticado en el dominio atacado, y que su sesión esté activa, no caducada. Dicha sesión se debe identificar mediante una cookie asociada a dicho dominio, para que el navegador la envíe junto con la petición sin necesidad de que el atacante la conozca.

Con estas premisas el atacante puede efectuar con éxito el ataque sobre el usuario, y su impacto y efectos serán los que permita la petición en cuestión:

  • Mover fondos, hacer transferencias o realizar pagos
  • Postear comentarios, estado o enviar mails o mensajes en nombre del usuario
  • Cambiar la contraseña u otros parámetros (de facturación por ejemplo) del usuario en dicho dominio
  • Pujar en una subasta, apostar al juego
  • …cualquier cosa que se pueda hacer en la Web

Existen mecanismos que previenen, al menos de una manera amplia, este ataque:

  • Solicitar en cada petición un token o valor aleatorio generado e informado al navegador del usuario en el momento del login de usuario o actualizado en cada petición. El sitio desde donde se realia el ataque cruzado no podrá deducir este valor. El token puede ser un valor aleatorio o el hash de una serie de valores o nonces o el contenido de una variable enviada por el servidor y firmada por este (como hace ASP.NET con la variable ViewState). Es obligación del servidor comprobar la corrección de este token en cada petición u operación sensible. CSRFGuard es una librería de OWASP para generar e interceptar este token.
  • El envío doble de cookies es una variante del anterior, en la que el token se corresponde con el identificador de sesión en la cookie. El servidor comprueba que ambas sean iguales en cada petición. Como el sitio de donde proviene el ataque no es del mismo dominio que el atacado, el script XSS no podrá tener acceso a la cookie y deducir el valor del token.
  • Solicitar al usuario credenciales o un CAPTCHA siempre que se invoquen peticiones HTTP que realicen operaciones sensibles.
  • Limitar el tiempo de vida las sesiones lo máximo posible sin que afecte a la usabilidad de la Web.
  • Comprobar la cabecera HTTP Referer para que la petición no venga de otro dominio distinto y exigir autenticación a aquellas peticiones que no tengan la cabecera o sea de otros dominios.
  • Utilizando add-ons como RequestPolicy o CsFire que evitan la navegación cruzada entre sitios o el envío de credenciales en la navegación cruzada
  • Cambiar las URLs con efectos secundarios de manera frecuente y aleatoria permite que los atacantes solo tengan un pequeño margen de tiempo (mientras dure la URL actual) para explotar la vulnerabilidad.

Mecanismos que no funcionan, o al menos de una manera completa, son los siguientes:

  • Las HTTPOnly cookies son aquellas que el navegador almacena y gestiona como cualquier otra cookie pero que no pueden ser referenciadas por el código script de la página. Siguen autenticando de la misma manera cada petición, porque es el navegador quien envía la cookie. El atacante no requiere conocer el identificador de sesión, solo que esté activo.
  • Utilizar para acciones con efectos secundarios solo métodos POST puede evitar algún patrón, como el de las imágenes. Pero las peticiones HTTP POST también pueden generarse mediante scripting o con un formulario enviado al correo del usuario.
  • Transacciones que se realizan con más de un paso o con más de una petición HTTP, tampoco son una solución en cuanto el atacante pueda deducir el orden en que se realizan.
  • Enviar las credenciales o el identificador de sesión en los parámetros de la URL, puede evitar el CSRF pero ¡expone nuestras credenciales en proxies, en históricos de navegación, a curiosos…!

Desde el punto de vista del usuario, existen también hábitos recomendados que ayudan a evitar el ataque:

  • Cerrar siempre la sesión de usuario una vez acabemos la navegación por una Web.
  • No hacer tareas especialmente sensibles a la vez, y mucho menos, navegando por otras Webs que no sean de confianza; o al menos, con el mismo navegador.
  • Evitar en la medida de lo posible el «Recordar mi usuario» de los sitios Web sensibles.
  • Desactivar la ejecución de Javascript en sitios que no sean de confianza, para evitar el XSS que invoque métodos POST de manera automática (aún podría engañar al usuario mediante ingeniería social para que «postease» el un formulario).
  • No mostrar imágenes o pulsar links en correos y documentos provenientes de spam o cuyo origen no sea conocido o de confianza.

Un saludo,

Referencias

CSRF en Wikipedia
CSRF en OWASP
Prevención del CSRF en OWASP
RFC 2616 en IETF

Licencia Creative Commons
Esta entrada de Juan Francisco Adame Lorite está publicada bajo una licencia Creative Commons Atribución-CompartirIgual 3.0 Unported.

Categorías: Seguridad, Web Etiquetas:

Login en GWT con OpenID y Spring Security

sábado, 4 febrero 2012, 3:00 9 comentarios

En la última entrada hablábamos de delegar la autenticación a terceros cuando la gestión de esas credenciales no aportase valor o fuese un requisito evitable. Entre las opciones que existen, una de ellas es el estándar OpenID, ya en su versión 2.0.

El objetivo de esta entrada es crear un aplicativo web que autentique la sesión en el servidor a través de OpenID mediante Google Accounts (Cuentas de Google). Todo ello lo integraremos dentro de la arquitectura de Spring Security. En este caso es un aplicativo web basado en la plataforma GWT; pero el 99% del peso funcional recae sobre Spring, por lo que es muy fácilmente portable a aplicativos web basados en otras tecnologías. Para el desarrollo de las llamadas a OpenID hemos utilizado OpenId4Java.

OpenId

OpenID es un estándar de autenticación descentralizado, que permite a una entidad (Proveedor OpendID) identificar usuarios para una tercera (Tercero de confianza) con la que establece esa asociación de confianza. La gestión de credenciales y los mecanismo de autenticación, quedan por completo bajo la responsabilidad de la primera entidad. El Tercero de confianza acepta las identificaciones que el Proveedor OpenID le ofrece; pero es el responsable de la autorización de acceso a los recursos propios. De esta manera, credenciales y autenticación quedan completamente separados e independientes de recursos y autorización. OpenID no describe el modo en que se realizan cualquiera de estas dos funciones, solo cubre el modo en que ambas entidades se comunican.

En OpenID interactúan entre sí:

  • Agente de usuario, que es el navegador que utiliza el usaurio
  • Tercero de confianza, que es el aplicativo o recurso al que el usuario desea acceder con las credenciales del Proveedor OpenID
  • Proveedor OpenID, que autentica al usuario y envía las credenciales validadas al Tercero de confianza para autorizar el acceso al recurso

La secuencia de interacción entre las diferentes partes es la siguiente:

  1. El usuario desea acceder a un recurso del Tercero de confianza. El tercero tiene que autorizar al usuario, por lo que debe identificarle primero. De entre las opciones para identificarle que se le ofrecen al usuario está a través de OpenID con un Proveedor.
  2. El usuario elige la opción de identificarse mediante OpenID
  3. El Tercero de confianza comienza la búsqueda o descubrimiento de los diferentes Proveedores OpenID
  4. El Tercero de confianza elige uno de los Proveedores y crea una asociación con él. Esta asociación contiene un secreto compartido que protege las sucesivas comunicaciones
  5. El Tercero de confianza, redirige al Agente de usuario al Proveedor OpenID. El Proveedor autentica al usuario y autoriza el acceso a atributos del usuario en el Proveedor si han sido solicitados
  6. El Proveedor OpenID redirige al Agente de usuario a una dirección de retorno preestablecida en el Tercero de confianza, junto con las credenciales del usuario
  7. El Tercero de confianza utiliza estas credenciales para autorizar el acceso al usuario

La solución

El usuario accede al aplicativo GWT y Spring Security, al no estar autenticado, le redirige a la pantalla de login. Entre las opciones de autenticación se le permite al usuario hacerlo mediante Google Accounts (Cuentas de Google). El usuario pulsa el botón o enlace en cuestión y es redirigido a Google para autenticarse.

Debemos primero redirigir al usuario hacia la web del Proveedor OpenID de Google. Para ello debemos crear la URL completa con todos los parámetros que nos redirija. Como queremos asociar la sesión del servidor a estas credenciales, haremos toda esta tarea en el lado del servidor mediante un método RPC de GWT que nos descubra el Proveedor OpenID, nos realice la asociación y nos genere la URL. Este método RPC nos devolverá la URL con los parámetros para redirigir al usuario al servidor de autenticación OpenID.

@UiHandler("googleAccountsButton")
void onGoogleAccountsButtonClick(ClickEvent event) {
	// Invocamos al servicio que nos genera la query string al servidor OpendID
	final LoginServiceAsync loginService = GWT.create(LoginService.class);
	loginService.requestOpenIdLogin(new AsyncCallback() {

		public void onFailure(Throwable error) {
			Window.alert("Error: "+error.getMessage());
		}

		public void onSuccess(String openIDQueryString) {
			// Redirigimos al servidor de autenticación OpenID
			Window.open(openIDQueryString, "_self", "");
		}
	});
}

En el lado del servidor, en la implementación del servicio que invocábamos antes:

  1. Descubrimos los servidores OpenID
  2. Creamos una asociación (que almacenamos en la sesión de usuario), para vincular la sesión del usuario actual con las credenciales que el servidor OpenID nos devuelva
  3. Generamos la URL para redirigir al usuario al servidor OpenID. Esta solicitud contiene además los atributos del usuario que queremos que el Proveedor OpenID nos devuelva. Los atributos del usuario no son más que datos personales asociados a la identidad del Proveedor OpenID. Se recupera mediante una extensión de OpenID, OpenID Attribute Exchange
package com.juanfran.server;

import java.util.List;

import org.openid4java.consumer.ConsumerManager;
import org.openid4java.discovery.DiscoveryInformation;
import org.openid4java.message.AuthRequest;
import org.openid4java.message.ax.FetchRequest;

import com.google.gwt.user.server.rpc.RemoteServiceServlet;
import com.juanfran.client.LoginService;

@SuppressWarnings("serial")
public class LoginServiceImpl extends RemoteServiceServlet implements
		LoginService {

	public String requestOpenIdLogin() throws Exception {
		ConsumerManager manager = new ConsumerManager();

		// Obtenemos la información del servidor de autenticación de OpenID
		@SuppressWarnings("unchecked")
		List discoveries = manager
				.discover("https://www.google.com/accounts/o8/id");

		// Creamos asociación con el servidor y la almacenamos en la sesión del
		// usuario
		DiscoveryInformation discovered = manager.associate(discoveries);
		this.getServletContext().setAttribute("discovered", discovered);

		// Creamos solicitud de atributos (OpenID Attribute Exchange)
		FetchRequest fetch = FetchRequest.createFetchRequest();
		fetch.addAttribute("FirstName", "http://axschema.org/namePerson/first",
				true);
		fetch.addAttribute("LastName", "http://axschema.org/namePerson/last",
				true);
		fetch.addAttribute("Email", "http://axschema.org/contact/email", true);
		fetch.addAttribute("Country",
				"http://axschema.org/contact/country/home", true);
		fetch.addAttribute("Language", "http://axschema.org/pref/language",
				true);
		fetch.addAttribute("data", "http://example.com/schema/data", true);

		// Creamos la petición de autenticación y le añadimos
		AuthRequest authRequest = manager.authenticate(discovered, String
				.format("http://%s:%d/Login/authResponse", this
						.getThreadLocalRequest().getServerName(), this
						.getThreadLocalRequest().getServerPort()));
		authRequest.addExtension(fetch);

		// Devolver la query string para redirigir al servidor de autenticación
		// de OpenID
		return authRequest.getDestinationUrl(true);
	}
}

El código onSuccess() de GWT nos redirige a la URL generada en el servidor. Navegamos al Proveedor OpenID, que en este caso es Google. El mecanismo por el que el servidor OpenID nos autentica está fuera del alcance de OpenID, puede ser cualquiera. Nuestro aplicativo confía en el servidor, en las credenciales que remita y en que el mecanismo de autenticación utilizado es seguro.
El Proveedor OpenID, una vez nos hemos autenticado, nos indica si queremos que el acceso al dominio del Tercero de confianza, 127.0.0.1 (porque está corriendo local en mi máquina), sea autorizado con estas credenciales (Cuenta de Google en este caso). Así mismo también nos informa de que, además de las credenciales (que es un token del que no se puede deducir ninguna información adicional), se solicita acceso a algunos atributos personales de las credenciales como son mi nombre completo, el mail y el idioma. No podemos autorizar unas cosas sí, y otras no; autorizamos o denegamos todo en su conjunto.

Solicitud de permisos en Cuentas de Google

Solicitud de permisos en Cuentas de Google

Si autorizamos el acceso, el navegador nos redirigirá de nuevo a la URL de retorno que pasamos como parámetro en la URL, «/Login/authResponse». En esta URL dentro de los filtros de Security Spring se encuentra escuchando nuestra implementación de AbstractAuthenticationProcessingFilter. Este filtro:

  1. Recoge los parámetros enviados por el servidor OpenID
  2. Verifica que la autenticación haya sido correcta
  3. Extrae los atributos personales del usuario
  4. Crea el objeto Authentication que contiene las credenciales y las devuelve para que Security Spring las gestione y asocie a la sesión del usuario

El filtro debe devolver una clase Authentication o una excepción si no ha sido posible validar la autenticación.

package com.juanfran.server;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.openid4java.association.AssociationException;
import org.openid4java.consumer.ConsumerManager;
import org.openid4java.consumer.VerificationResult;
import org.openid4java.discovery.DiscoveryException;
import org.openid4java.discovery.DiscoveryInformation;
import org.openid4java.discovery.Identifier;
import org.openid4java.message.AuthSuccess;
import org.openid4java.message.MessageException;
import org.openid4java.message.ParameterList;
import org.openid4java.message.ax.AxMessage;
import org.openid4java.message.ax.FetchResponse;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;

/**
 * Clase que implementa el filtro de autenticación HTTP para capturar las
 * respuestas con las credenciales del servidor OpenID.
 *
 * @author juanfran
 *
 */
public class OpenIdAuthenticationFilter extends
		AbstractAuthenticationProcessingFilter {

	/**
	 * Constructor del filtro de autenticación
	 *
	 * @param url
	 *            URL a la que se espera que lleguen las peticiones con las
	 *            credenciales para autenticar. En nuestro caso, es la URL de
	 *            retorno que se le indica al OpenID Authentication server para
	 *            que devuelva las credenciales una vez autenticado.
	 */
	public OpenIdAuthenticationFilter(String url) {
		super(url);
	}

	@Override
	public Authentication attemptAuthentication(HttpServletRequest request,
			HttpServletResponse response) throws AuthenticationException,
			IOException, ServletException {
		// Creamos un tipo ParameterList
		ParameterList openIDParams = new ParameterList(
				request.getParameterMap());

		// Recuperamos la asociación de la sesión del usuario
		DiscoveryInformation discovered = (DiscoveryInformation) this
				.getServletContext().getAttribute("discovered");

		// Creamos la URL con parámetros incluidos
		StringBuffer receivingURL = request.getRequestURL();
		String queryString = request.getQueryString();
		if (queryString != null && queryString.length() > 0)
			receivingURL.append("?").append(request.getQueryString());

		try {
			ConsumerManager manager = new ConsumerManager();

			// Verificamos la
			VerificationResult verification = manager.verify(
					receivingURL.toString(), openIDParams, discovered);

			// Obtenemos las credenciales autenticadas
			Identifier verified = verification.getVerifiedId();

			// Si hay credenciales es porque se ha autenticado
			if (verified != null) {
				// Obtenemos los atributos de la cuenta
				AuthSuccess authSuccess = (AuthSuccess) verification
						.getAuthResponse();
				if (authSuccess.hasExtension(AxMessage.OPENID_NS_AX)) {
					FetchResponse fetchResp = (FetchResponse) authSuccess
							.getExtension(AxMessage.OPENID_NS_AX);

					String name = fetchResp.getAttributeValue("FirstName")
					 + " " + fetchResp.getAttributeValue("LastName");
					String mail = fetchResp.getAttributeValue("Email");

					// Almacenamos el mail en sesión por si queremos utilizarlo posteriormente
					request.getSession().setAttribute("mail", mail);

					// Creamos la clase Authentication que almacena las credenciales
					Authentication verifiedAuth = new OpenIdAuthentication(
							mail, verified.getIdentifier());
					verifiedAuth.setAuthenticated(true);
					return verifiedAuth;
				} else {
					// Si no se recogieron atributos
					throw new AuthenticationServiceException(
							"Attributes not retrieved");
				}
				// Si no las hay
			} else {
				throw new BadCredentialsException("Not authenticated");
			}

		} catch (MessageException e) {
			e.printStackTrace();
			throw new AuthenticationServiceException(e.getMessage(), e);
		} catch (DiscoveryException e) {
			e.printStackTrace();
			throw new AuthenticationServiceException(e.getMessage(), e);
		} catch (AssociationException e) {
			e.printStackTrace();
			throw new AuthenticationServiceException(e.getMessage(), e);
		}
	}

}

La clase Authentication creada es una instancia de OpenIdAuthentication. Esta clase implementa Authentication. También implementa Principal, pero es solo para poder devolverse a si misma en el método getPrincipal() y ahorrarnos la implementación de una clase. Por defecto, a todos los usuarios se les asigna el rol ROLE_USER.

package com.juanfran.server;

import java.security.Principal;
import java.util.ArrayList;
import java.util.Collection;

import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;

/**
 * Clase que contiene las credenciales
 *
 */
@SuppressWarnings("serial")
public class OpenIdAuthentication implements Authentication, Principal {

	private boolean authenticated = false;
	Collection authorities;
	private String name;
	private String openId;

	/**
	 * Constructor.
	 *
	 * @param name
	 *            nombre
	 * @param openId
	 *            URI que identifica la usuario
	 */
	public OpenIdAuthentication(String name, String openId) {
		this.name = name;
		this.openId = openId;
		authorities = new ArrayList();
		//Por defecto se le asigna el role "ROLE_USER"
		authorities.add(new SimpleGrantedAuthority("ROLE_USER"));
	}

	/**
	 * Nombre (de Principal)
	 */
	public String getName() {
		return name;
	}

	/**
	 * Authority o permiso otorgado a estas credenciales
	 */
	public Collection getAuthorities() {
		return authorities;

	}

	/**
	 * Credenciales
	 */
	public Object getCredentials() {
		return openId;
	}

	/**
	 * Detalles de usuario. No implementado
	 */
	public Object getDetails() {
		return null;
	}

	/**
	 * Identidad identificada con estas credenciales
	 */
	public Object getPrincipal() {
		return this;
	}

	/**
	 * Indica cuando ha sido autenticado y verificado
	 */
	public boolean isAuthenticated() {
		return this.authenticated;
	}

	/**
	 * Indica cuando ha sido autenticado y verificado
	 */
	public void setAuthenticated(boolean arg0) throws IllegalArgumentException {
		this.authenticated = arg0;

	}
}

Ahora debemos configurar Spring Security y el Servlet de la llamada RPC en el web.xml.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE web-app
    PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
    "http://java.sun.com/dtd/web-app_2_3.dtd">

<web-app>

	<!-- Spring Security -->
	<filter>
		<filter-name>springSecurityFilterChain</filter-name>
		<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
	</filter>
	<filter-mapping>
		<filter-name>springSecurityFilterChain</filter-name>
		<url-pattern>/*</url-pattern>
	</filter-mapping>
	<listener>
		<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
	</listener>

	<!-- Servlets -->
	<servlet>
		<servlet-name>loginServlet</servlet-name>
		<servlet-class>com.juanfran.server.LoginServiceImpl</servlet-class>
	</servlet>
	<servlet-mapping>
		<servlet-name>loginServlet</servlet-name>
		<url-pattern>/Login/login</url-pattern>
	</servlet-mapping>

	<!-- Default page to serve -->
	<welcome-file-list>
		<welcome-file>Hello.jsp</welcome-file>
	</welcome-file-list>

</web-app>

Por último, creamos todas las Beans y contexto de Spring en el fichero applicationContext.xml.
Como nuestro filtro OpenIdAuthenticationFilter sustituye al tradicional filtro web de usuario y contraseña UsernamePasswordAuthenticationFilter, no podemos utilizar auto-config="true". Esto nos obliga a definir el entry-point. Es una particularidad de configuración de Spring.
El filtro OpenIdAuthenticationFilter tiene referencias a los Handler correspondientes en caso de autenticación correcta y errónea, junto con sus URL de cada caso y su referencia al AuthenticationManager.
Para separar la parte pública de la privada, la aplicación GWT se ha dividido en dos módulos diferenciados, Login y Hello. Login es el encargado de las tareas de autenticación, por eso es accesible anónimamente; y Hello es la parte protegida, que solo es accesible una vez autenticado y autorizado.

<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/security"
	xmlns:beans="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
                        http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.1.xsd">

	<http auto-config="false" entry-point-ref="loginUrlAuthenticationEntryPoint">
		<intercept-url pattern="/Login.html" access="IS_AUTHENTICATED_ANONYMOUSLY" />
		<intercept-url pattern="/Login/**" access="IS_AUTHENTICATED_ANONYMOUSLY" />
		<intercept-url pattern="/Hello/**" access="ROLE_USER" />
		<intercept-url pattern="/Hello.jsp" access="ROLE_USER" />
		<intercept-url pattern="/**" access="IS_AUTHENTICATED_ANONYMOUSLY" />
		<logout logout-success-url='/Login.html?gwt.codesvr=127.0.0.1:9997' />
		<custom-filter position="FORM_LOGIN_FILTER" ref="authenticationFilter" />
	</http>

	<beans:bean id="loginUrlAuthenticationEntryPoint"
		class="org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint">
		<beans:property name="loginFormUrl"
			value="/Login.html?gwt.codesvr=127.0.0.1:9997" />
	</beans:bean>

	<beans:bean id="authenticationFilter"
		class="com.juanfran.server.OpenIdAuthenticationFilter">
		<beans:constructor-arg type="String"
			value="/Login/authResponse" />
		<beans:property name="authenticationManager" ref="authenticationManager" />
		<beans:property name="authenticationFailureHandler"
			ref="failureHandler" />
		<beans:property name="authenticationSuccessHandler"
			ref="successHandler" />
	</beans:bean>

	<beans:bean id="successHandler"
		class="org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler">
		<beans:property name="defaultTargetUrl"
			value="/Hello.jsp?gwt.codesvr=127.0.0.1:9997" />
	</beans:bean>
	<beans:bean id="failureHandler"
		class="org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler">
		<beans:property name="defaultFailureUrl"
			value="/Login.html?gwt.codesvr=127.0.0.1:9997" />
	</beans:bean>

	<authentication-manager alias="authenticationManager" />
</beans:beans>

El host y el puerto en que está desplegado el aplicativo se deben a que está configurado para ser ejecutado localmente por el servidor Jetty de GWT. Y el parámetro «gwt.codesvr=127.0.0.1:9997» en todas las URLs se incluye para permitir el debug.

Un saludo.

Licencia Creative Commons
Esta entrada de Juan Francisco Adame Lorite está bajo una licencia Creative Commons Atribución-CompartirIgual 3.0 Unported.

Categorías: Desarrollo, Seguridad, Web Etiquetas: , ,

Autenticación y Gestión de Credenciales ¿es realmente necesario?

miércoles, 25 enero 2012, 21:03 1 comentario

La autenticación de usuarios es uno de los elementos claves de toda aplicación; ya no solo por aspectos de seguridad, también por lo fundamental que es a la hora de poder diferenciar y mejorar la experiencia de usuario.

Pero la gestión de esas credenciales y la responsabilidad que conlleva son una tarea de gran complejidad y coste operativo, innecesario muchas veces. Las situaciones en las que es realmente estratégico conocer y gestionar esos datos de identificación real se reducen a casos muy aislados, por ejemplo:

  • Redes sociales en los que la identidad del usuario sea relevante, como son las redes sociales profesionales.
  • Servicios que requieren facturación directa al usuario.
  • Servicios u operativas que legalmente exijan el registro de esos datos, como por ejemplo las páginas de juego.

En la gran mayoría de los casos, no es estratégico o no aporta valor al negocio el conocer y gestionar esas credenciales. Y por el contrario, solo aportan tareas y complejidades operativas:

  • Registro del fichero de datos personales en la Agencia de Protección de Datos (esto es particular de España, pero estoy convencido que es similar en otros muchos países).
  • Implantación del Reglamento de Medidas de Seguridad de los ficheros de datos de carácter personal (igualmente, propio de España, pero aplicable de manera análoga en otros países).
  • Restricciones en cuanto a la ubicación física de componentes si hubiese algún tipo de restricción legal a respecto (nuevamente, en España, la legislación de protección de datos no permite ubicar el fichero fuera del país, al menos sin unas fuertes limitaciones, ver Título V de la LOPD).
  • Controles de seguridad y metodologías de desarrollo más complejas y costosas, así como procedimientos de Gestión de Incidencias.

Y se acompaña a su vez de un conjunto de riesgos a tener en cuenta también:

  • Riesgo reputacional, que sufre la imagen de la empresa o su credibilidad técnica y operativa si sus sistemas de autenticación son rotos.
  • Riesgo operacional, que conlleva el hacerse cargo de los costos o pérdidas debidas al fraude cometido.
  • Riesgo operativo por disponibilidad de servicio, cuando nos veamos obligados a parar el servicio durante las tareas de investigación y subsanación de un problema de seguridad.
  • Riesgo legal, cuando el fraude conlleva además responsabilidades legales.

Seguro que Sony nos puede hablar muy bien del impacto de todos ellos.

Por estos motivos, el acompañar a nuestra aplicación de funcionalidades de registro de usuario, así como de identificación, es en muchas ocasiones innecesario. No estoy queriendo decir que no personalicemos el comportamiento del aplicativo (Gestión de Perfiles de Usuario o Personalización), ni que no protejamos las diferentes funcionalidades o servicios (Autorización); que siguen siendo muy necesarios. Me estoy refiriendo a hacer uso de los sistemas de autenticación y credenciales de terceros para usarlos en nuestro aplicativo o servicio.

Esta práctica empieza a ser muy común. Usando estándares como OpenID o OAuth, los diferentes servicios Internet ceden la gestión de credenciales a terceros de confianza, como Google y Facebook entre otros, y se aprovechan de sus credenciales para autenticar a los usuarios, para gestionar su perfil o para autorizar los diferentes servicios que proveen.

Estas prácticas tienen una serie de ventajas inmediatas:

  • Es más cómodo para el usuario, que no tiene recordar unas nuevas credenciales, ni tiene que registrarse en el servicio. Simplemente usa las credenciales del tercero donde ya se ha registrado. La personalización del servicio o gestión del perfil del usuario, así como la autorización de acceso a los diferentes servicios y recursos, se sigue haciendo pero con estas credenciales.
  • Transferimos gran parte de los riesgos antes expuestos a este tercero, responsable ahora de la gestión e identificación de las credenciales. No olvidemos, de todas maneras, que mecanismos de seguridad de control de acceso y autorización siguen de nuestro lado, con todo lo que eso implica.
  • Evitamos algunas de las tareas y complejidades operativas de las que hablábamos antes. En el caso de los datos de carácter personal esto es así siempre y cuando, una vez autenticados con la credenciales del tercero, no almacenemos datos de tipo personal; simplemente hagamos uso de los que nos llegan con las credenciales (ver artículo 12 LOPD).
  • En algunos casos, mediante OpenId Attributes Exchange por ejemplo, es posible junto con las credenciales recibir algunos datos (de carácter personal, por cierto) del usuario, como son mail, nombre y nacionalidad. Estos datos, gestionados por el proveedor de credenciales y actualizados por el usuario, pueden ser explotados por nuestro servicio de una manera muy cómoda para el usuario, sin registros ni formularios. Ahora sí, cuando los solicitemos, el proveedor de credenciales consultará al usuario el acceso a los mismos por nuestra parte; lo que, además de una exigencia legal y de política del proveedor, es un mecanismo de transparencia y credibilidad muy bueno cara al usuario, en mi opinión.
  • En el caso de aplicaciones Web, toda la lógica de autenticación contra el proveedor puede implementarse de manera segura en el navegador, no habiendo necesidad de hacerlo en el lado del servidor. Esto tiene ventajas en la carga y ancho de banda que soporta nuestra infraestructura.

Existen ya muchos servicios de Internet que autentican ya mediante Google Accounts, como es Rescue Time, o mediante Facebook, como Endomondo. El punto clave de esta solución es elegir correctamente a este tercero de confianza, y sobre todo hacerlo en línea con la política de alianzas o independencia que queramos mostrar (cara al usuario hacemos palpable y evidente que hemos cedido la gestión de identidades a un tercero).

Por lo tanto, ¿es necesario hacernos cargo de la gestión de credenciales?. La respuesta es depende. Sí, cuando las credenciales son parte estratégica de nuestro negocio o cuando exista algún imperativo legal que nos obligue a hacerlo; no, cuando no aporte ningún valor a nuestro negocio.

Un saludo.

Licencia Creative Commons
Esta obra de Juan Francisco Adame Lorite está bajo una licencia Creative Commons Atribución-CompartirIgual 3.0 Unported.

Categorías: Seguridad Etiquetas:
A %d blogueros les gusta esto: