Archivo

Archive for febrero 2012

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: , ,
A %d blogueros les gusta esto: