Autenticación en aplicaciones Flex con Spring Security 3



Spring Security es un framework que se centra en proveer mecanismos de autenticación y autorización para aplicaciones basadas en Spring. Mediante el paquete de integración de Spring con BlazeDS spring-flex, conjuntamente con Spring Security, podemos añadir seguridad a nuestras aplicaciones Flex/BlazeDS.

En este artículo veremos, a través de un ejemplo, cómo podemos utilizar el mecanismo de autenticación de Spring Security desde una aplicación Flex. En la aplicación que desarrollaremos tenemos la información de los usuarios y los roles asignados a éstos en una base de datos MySQL, a la cual accederemos utilizando JPA e Hibernate. Desde Flex invocaremos el servicio de autenticación de Spring Security, el cual utilizará la información de los usuarios y roles en la base de datos para comprobar las credenciales de usuario suministradas, si el proceso termina exitosamente tendremos en la aplicación Flex la información del usuario autenticado, en caso contrario se nos notificará del error.

Para el desarrollo de la aplicación utilizaremos:

  • Spring 3.0.2
  • Spring Security 3.0.3
  • Spring-Flex 1.5.0.M1
  • BlazeDS 4.0.0.14931
  • Flex 4
  • Flash Builder 4 plugin en Eclipse IDE for J2EE (Galileo)
  • RobotLegs 1.1.2

La aplicación la desarrollaremos en dos proyectos, un proyecto Web J2EE para el back-end y un proyecto Flex para la interface visual.

El back-end

Comenzaremos por la creación del proyecto Web J2EE

  1. File … Import … Web->War File
  2. WAR File: El camino del archivo blazeds.war
  3. Web Project: FlexSpringSecurity
  4. Target Runtime: Alguno que se haya configurado previamente
  5. War Import: Web Libraries. No seleccionamos ningún paquete

Copiamos en la carpeta WebContent\WEB-INF\lib los archivos de Spring, Spring Security, Spring-Flex, el driver JDBC de MySQL. El listado de los archivos de la carpeta se puede ver aquí.

Como utilizamos el paquete de integración de Spring con BlazeDS solamente necesitamos en la carpeta WebContent\WEB-INF\flex el archivo services-config.xml. También necesitamos modificar el archivo web.xml en WebContent\WEB-INF, en el que se hace referencia a los archivos web-application-config.xml y web-application-context-config.xml, donde se configura el contexto de Spring.

Comenzaremos por crear la capa que accederá a la base de datos para obtener la información de usuarios y roles.

User.java
[java]
package com.nonocarballo.flexspringsecurity.valueobject;

import java.util.List;

import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.ManyToMany;

@Entity
public class User {

private Long id;
private String fullName;
private String userName;
private String password;

@Id
@GeneratedValue
public Long getId() {
return id;
}

public void setId(Long id) {
this.id = id;
}

public String getFullName() {
return fullName;
}

public void setFullName(String fullName) {
this.fullName = fullName;
}

public String getUserName() {
return userName;
}

public void setUserName(String userName) {
this.userName = userName;
}

public String getPassword() {
return password;
}

public void setPassword(String password) {
this.password = password;
}

private List roleList;

@ManyToMany(cascade=CascadeType.ALL, fetch=FetchType.LAZY)
public List getRoleList() {
return roleList;
}

public void setRoleList(List roleList) {
this.roleList = roleList;
}
}
[/java]

Role.java
[java]
package com.nonocarballo.flexspringsecurity.valueobject;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;

@Entity
public class Role {

private Long id;
private String name;

@Id
@GeneratedValue
public Long getId() {
return id;
}

public void setId(Long id) {
this.id = id;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}
}
[/java]

RoleType.java
[java]
package com.nonocarballo.flexspringsecurity.valueobject;

public class RoleType {
public static final String ADMIN = “ADMINISTRATOR”;
public static final String USER = “USER”;
public static final String GEST = “GEST”;
}
[/java]

IUserService.java
[java]
package com.nonocarballo.flexspringsecurity.service;

import java.util.List;
import com.nonocarballo.flexspringsecurity.valueobject.User;

public interface IUserService {
public List loadAllUsers();
public void addUser(User user);
public User loabByUserName(String username);
}
[/java]

UserService.java
[java]
package com.nonocarballo.flexspringsecurity.service.impl;

import java.util.List;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.PersistenceContextType;
import javax.persistence.Query;

import org.springframework.flex.remoting.RemotingDestination;
import org.springframework.flex.remoting.RemotingExclude;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;

import com.nonocarballo.flexspringsecurity.service.IUserService;
import com.nonocarballo.flexspringsecurity.valueobject.User;

@Repository
@RemotingDestination
public class UserService implements IUserService {

private EntityManager entityManager;

@RemotingExclude
public EntityManager getEntityManager() {
return entityManager;
}

@RemotingExclude
@PersistenceContext(type = PersistenceContextType.EXTENDED)
public void setEntityManager(EntityManager entityManager) {
this.entityManager = entityManager;
}

@SuppressWarnings(“unchecked”)
@Override
public List loadAllUsers() {
// TODO Auto-generated method stub
return entityManager.createQuery(“from User u”).getResultList();
}

@Override
@Transactional
public User loabByUserName(String username) {
Query userQuery = entityManager.createQuery(“from User u where u.userName = :user”);
userQuery.setParameter(“user”, username);
User user = (User) userQuery.getSingleResult();
return user;
}

@Override
@Transactional
public void addUser(User user) {
// TODO Auto-generated method stub
entityManager.persist(user);
}
}
[/java]

Escribimos ahora un código en forma de test para generar las tablas de la base de datos y llenarlas con alguna información.

UserServiceTest.java
[java]
package com.nonocarballo.flexspringsecurity.test;

import static org.junit.Assert.*;

import java.util.ArrayList;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import com.nonocarballo.flexspringsecurity.service.IUserService;
import com.nonocarballo.flexspringsecurity.valueobject.Role;
import com.nonocarballo.flexspringsecurity.valueobject.RoleType;
import com.nonocarballo.flexspringsecurity.valueobject.User;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations={“classpath:/spring/web-application-context-config.xml”})
public class UserServiceTest {

private IUserService userService;

public IUserService getUserService() {
return userService;
}

@Autowired
public void setUserService(IUserService userService) {
this.userService = userService;
}

@Test
public void addUserTest(){

User user = new User();
user.setFullName(“Nono Carballo Escalona”);
user.setUserName(“nono”);
user.setPassword(“mypassword”);

ArrayList roleList = new ArrayList();

Role role = new Role();
role.setName(RoleType.GEST);
roleList.add(role);

role = new Role();
role.setName(RoleType.USER);
roleList.add(role);

role = new Role();
role.setName(RoleType.ADMIN);
roleList.add(role);

user.setRoleList(roleList);

userService.addUser(user);

user = new User();
user.setFullName(“Nono Carballo Escalona1”);
user.setUserName(“nono1”);
user.setPassword(“mypassword1”);

roleList = new ArrayList();

role = new Role();
role.setName(RoleType.GEST);
roleList.add(role);

user.setRoleList(roleList);

userService.addUser(user);

user = new User();
user.setFullName(“Nono Carballo Escalona2”);
user.setUserName(“nono2”);
user.setPassword(“mypassword2”);

roleList = new ArrayList();

role = new Role();
role.setName(RoleType.USER);
roleList.add(role);

user.setRoleList(roleList);

userService.addUser(user);

ArrayList userList = (ArrayList) userService.loadAllUsers();

assertTrue(userList.size() == 3);

}
}
[/java]

Para la interacción entre spring-flex y Spring Security debe registrarse en el archivo web.xml el un filtro del tipo DelegatingFilterProxy.

[xml]

springSecurityFilterChain
org.springframework.web.filter.DelegatingFilterProxy


springSecurityFilterChain
/*

[/xml]

Debemos crear una clase a través de la cual Spring Security recuperará de la base de datos la información del usuario que intenta autenticarse. Esta clase implementará la interface UserDetailsService, que tiene un único método, loadUserByUsername, el cual será invocado por el proveedor de autenticación del framework.

MyUserDetailsService.java
[java]
package com.nonocarballo.flexspringsecurity.service;

import java.util.ArrayList;
import java.util.List;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.GrantedAuthorityImpl;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;

import com.nonocarballo.flexspringsecurity.valueobject.AuthenticatedUser;
import com.nonocarballo.flexspringsecurity.valueobject.Role;
import com.nonocarballo.flexspringsecurity.valueobject.User;

@Repository
public class MyUserDetailsService implements UserDetailsService {

private IUserService userService;

public IUserService getUserService() {
return userService;
}

@Autowired
public void setUserService(IUserService userService) {
this.userService = userService;
}

@Override
@Transactional
public UserDetails loadUserByUsername(String arg0)
throws UsernameNotFoundException, DataAccessException {
User user = userService.loabByUserName(arg0);
if(user == null){
throw new
UsernameNotFoundException(“User “+arg0+” not found.”);
}
List roleList=(List) user.getRoleList();
ArrayList grantedAuthorities = new
ArrayList();
for(Role role : roleList){
grantedAuthorities.add(new GrantedAuthorityImpl(role.getName()));
}
return new
org.springframework.security.core.userdetails.User(user.getUserName(),
user.getPassword(), true, true, true, true, grantedAuthorities);

}
}
[/java]

El objeto de tipo org.springframework.security.core.userdetails.User contiene la información que llegará a la aplicación Flex, en este caso el nombre de usuario y los roles que tiene asignados.

Ahora debemos informar a Spring Security que use nuestra clase y no la que trae el framework, esto se hace en el archivo web-application-context-config.xml, la configuración final queda como sigue:

[xml]





[/xml]

Y finalmente de debemos informar a spring-flex (en el archivo web-application-config.xml) que utilice el servicio de autenticación de Spring Security.

[xml]



[/xml]

La aplicación Flex

Crearemos un proyecto Flex que llamaremos FlexSpringSecurityUI con las siguientes características:

  • Application Type: Web
  • Flex SDK Version: Flex 4.0
  • Application Server Type: J2EE
  • Remote access service: BlazeDS
  • RootFolder: el camino a la carpeta FlexSecurity/WebContent
  • Root URL: el URL donde se desplegó el proyecto FlexSpringSecurity
  • Context root: flexspringsecurity

Nuestra aplicación Flex contendrá un formulario para autenticación y un botón para entrar (login) y salir (logout).

login

Crearemos un objeto de tipo ChannelSet y le adicionaremos un canal de tipo AMFChannel, luego para invocar al servicio de autenticación de Spring Security desde Flex utilizaremos el método login de lal objeto ChannelSet, lo invocaremos pasándole como argumentos el nombre de usuario y la contraseña introducidos en el formulario. En esta aplicación recuperaremos el URL donde está desplegado el back-end en tiempo de ejecución, desde un archivo xml, y utilizaremos esa información para crear programáticamente el canal e insertarlo en el ChannelSet. Nos auxiliaremos del framework Robotlegs.

channels.xml
[xml]



http://localhost:8080/flexspringsecurity


https://localhost:8080/flexspringsecurity


[/xml]

[as]
package com.nonocarballo.flexspringsecurity.command
{
import mx.messaging.ChannelSet;
import mx.messaging.channels.AMFChannel;
import mx.rpc.CallResponder;
import mx.rpc.events.ResultEvent;
import mx.rpc.http.HTTPService;

import org.robotlegs.mvcs.Command;

public class LoadConfigCommand extends Command
{
public function LoadConfigCommand()
{
super();
}

private var responder:CallResponder = new CallResponder();

override public function execute():void{
var service:HTTPService = new HTTPService();
service.url = “config/channels.xml”;
service.resultFormat = “e4x”;

responder.addEventListener(ResultEvent.RESULT, resultHandler);
responder.token = service.send();
}

private function resultHandler(event: ResultEvent):void{
var xml:XML = event.result as XML;

var channelSet:ChannelSet = new ChannelSet();
for each(var item:XML in xml..channel){
if(item.@type==”amf”){
var amfChannel:AMFChannel = new AMFChannel();
amfChannel.uri = item.uri+”/messagebroker/amf”;
channelSet.addChannel(amfChannel);
break;
}
}
injector.mapValue(ChannelSet, channelSet);
}
}
}
[/as]

Cuando presionamos el botón “Entrar” invocamos el método login del objeto ChannelSet con la credencial del usuario:

[as]
package com.nonocarballo.flexspringsecurity.command
{
import com.nonocarballo.flexspringsecurity.event.AuthenticatedEvent;
import com.nonocarballo.flexspringsecurity.event.LoginEvent;

import flash.events.Event;

import mx.messaging.ChannelSet;
import mx.rpc.CallResponder;
import mx.rpc.events.FaultEvent;
import mx.rpc.events.ResultEvent;

import org.robotlegs.mvcs.Command;

public class LoginCommand extends Command
{
public function LoginCommand()
{
super();
}

[Inject]
public var channelSet:ChannelSet;

[Inject]
public var event:LoginEvent;

private var responder:CallResponder = new CallResponder();

override public function execute():void{
if(!channelSet.authenticated){
responder.addEventListener(ResultEvent.RESULT, resultHandler);
responder.addEventListener(FaultEvent.FAULT, faultHandler);
responder.token = channelSet.login(event.username,
event.password);
}
}

private function resultHandler(event:ResultEvent):void{
dispatch(new AuthenticatedEvent(AuthenticatedEvent.AUTHENTICATED,
event.result));
}

private function faultHandler(event:FaultEvent):void{
dispatch(new AuthenticatedEvent(AuthenticatedEvent.LOGIN_FAILURE));
}
}
}
[/as]

Si la credencial de usuario no es válida obtendremos:

login-fail

En caso contrario:

login-ok-pre

Más información del usuario en Flex

Con el objeto org.springframework.security.core.userdetails.User solo obtenemos información del nombre de usuario y los roles, pero casi nunca esa información es suficiente. Haremos los cambios necesarios para poder traer hacia la aplicación Flex información extra del usuario que se ha autenticado.

Primeramente debemos crear nuestra propia implementación de UserDetails y añadir toda la información que necesitemos, en nuestro caso solo añadimos el nombre completo del usuario (fullname).

AuthenticatedUser.java
[java]
package com.nonocarballo.flexspringsecurity.valueobject;

import java.util.Collection;

import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

@SuppressWarnings(“serial”)
public class AuthenticatedUser implements UserDetails {

private String username;
private String password;
private String fullname;

public String getFullname() {
return fullname;
}

private Collection authorities;

@Override
public Collection getAuthorities() {
// TODO Auto-generated method stub
return authorities;
}

@Override
public String getPassword() {
// TODO Auto-generated method stub
return password;
}

@Override
public String getUsername() {
// TODO Auto-generated method stub
return username;
}

@Override
public boolean isAccountNonExpired() {
// TODO Auto-generated method stub
return true;
}

@Override
public boolean isAccountNonLocked() {
// TODO Auto-generated method stub
return true;
}

@Override
public boolean isCredentialsNonExpired() {
// TODO Auto-generated method stub
return true;
}

@Override
public boolean isEnabled() {
// TODO Auto-generated method stub
return true;
}

public AuthenticatedUser() {
super();
// TODO Auto-generated constructor stub
}

public AuthenticatedUser(String username, String fullname, String password,
Collection authorities) {
super();
this.username = username;
this.fullname = fullname;
this.password = password;
this.authorities = authorities;
}
}
[/java]

Luego necesitamos crear nuestro propio interceptor de mensaje implementando MessageInterceptor. Esta clase es la encargada de extraer del resultado del proceso de autenticación la información que llegará a la aplicación Flex una vez concluido éste exitosamente.

MyLoginMessageInterceptor.java
[java]
package com.nonocarballo.flexspringsecurity.service;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.flex.core.MessageInterceptor;
import org.springframework.flex.core.MessageProcessingContext;
import org.springframework.stereotype.Service;

import flex.messaging.messages.CommandMessage;
import flex.messaging.messages.Message;

@Service
public class MyLoginMessageInterceptor implements MessageInterceptor {

private final Log logger = LogFactory.getLog(getClass());

@Override
public Message postProcess(MessageProcessingContext context, Message inputMessage,
Message outputMessage) {
// TODO Auto-generated method stub
if (inputMessage instanceof CommandMessage &&
((CommandMessage) inputMessage).getOperation() ==
CommandMessage.LOGIN_OPERATION) {
if (outputMessage.getBody()!= null) {
outputMessage.setBody(
MyAuthenticationResultUtils.getAuthenticationResult());
}
}
return outputMessage;
}

@Override
public Message preProcess(MessageProcessingContext context, Message inputMessage) {
// TODO Auto-generated method stub
return inputMessage;
}

}
[/java]

Como vemos, esta clase hace uso de MyAuthenticationResultUtils, la cual crearemos también, y donde ocurre la creación del objeto que llegará a la aplicación Flex en la propiedad result de la clase ResultEvent.

[java]
package com.nonocarballo.flexspringsecurity.service;

import java.util.HashMap;
import java.util.Map;

import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;

import com.nonocarballo.flexspringsecurity.valueobject.AuthenticatedUser;

public abstract class MyAuthenticationResultUtils {

public static Map getAuthenticationResult() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication == null) {
return null;
}
Map authenticationResult = new HashMap();
authenticationResult.put(“name”, authentication.getName());
String[] authorities = new String[authentication.getAuthorities().size()];
int i=0;
for (GrantedAuthority granted : authentication.getAuthorities()) {
authorities[i++] = granted.getAuthority();
}
authenticationResult.put(“authorities”, authorities);
authenticationResult.put(“fullname”, ((AuthenticatedUser)authentication.getPrincipal()).getFullname());
return authenticationResult;
}
}
[/java]

Finalmente debemos registrar nuestro interceptor de mensaje personalizado:

[xml]




[/xml]

Cuando nos autenticamos nuevamente obtenemos:

login-ok

El código fuente de los proyectos se puede descargar aquí.

Conclusiones

Spring Security provee un mecanismo flexible de autenticación (y control de acceso) para aplicaciones basadas en Spring. En nuestra aplicación de ejemplo utilizamos JPA e Hibernate para acceder a la información de los usuarios.

Utilizando el paquete de integración de Spring con BlazeDS y Spring Security podemos implementar mecanismos de autenticación en nuestras aplicaciones Flex/BlazeDS.

9 Comentarios

  1. Pingback: Tweets that mention MadeInFlex » Blog Archive » Autenticación en aplicaciones Flex con Spring Security 3 -- Topsy.com

  2. Agustin Lopez

    Great stuff with this… First time I see using Spring security with a flex application which it might be useful in any big application that requires several roles.

    Thanks for sharing it.

  3. anónimo

    Me parece un excelente ejemplo. Me gustaría saber si existe alguna guia / libro (Actualizado si es posible ) de Robotleg.

    Intente Contactarte pero no existía alguna pagina o información mas allá de quien hizo esta guía , pues busque por “Equipo” y no aparecía Nono F. Carballo Escalona.

  4. perezagua

    Antes de nada darte la enhorabuena por el articulo.
    Después de examinar el tutorial, ¿Cual es la forma mas adecuada de utilizar esta información en flex?. Es decir ¿cual seria la mejor forma de por ejemplo mostrar un botón o no dependiendo del role asignado del usuario que accede?
    Muchas gracias y un saludo.

  5. Carlos

    Hola,

    Tengo una duda con esta aproximación. Si ya he hecho el proceso de login en la aplicación, pero recargo la url, ¿cómo puedo hacer para que recuerde mis credenciales y no me vuelva a pedir la pantalla de login?.

    Muchas gracias y un saludo,
    Carlos

  6. Sergio

    Cada vez veo más complicado programar en Java con tantos Frameworks, que además tambien cambian en cada nueva versión…

  7. cecilia

    Excelente articulo! Como estoy iniciandome en Robotlegs, me gustaria conocer los fuentes de Flex de este articulo. Es decir, como organizar el proyecto con los Mediators, Etc. Muchas Gracias!Cecilia

  8. cecilia

    Una consulta: el servicio myUserDetailsService no entiendo donde se define a que clase MyUserDetailsService pertenece.
    Intente definir el bean asociandolo a su clase pero no funciono: en mi caso se llama el servicio userDetailsServiceImpl

    Gracias!

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Acerca de Made In Flex

Made In Flex es una comunidad de desarrolladores de Apache Flex creada en 2006.

Apache Flex, anteriormente conocido como Adobe Flex, es un SDK (kit de desarrollo de software) para crear aplicaciones enriquecidas - multiplataforma basadas en Adobe Flash donado por Adobe a la fundación Apache in 2011 y promocionado a proyecto de primer nivel en Diciembre de 2012.

Actualmente estamos cambiando muchos aspectos del sitio web para ofrecer un sitio útil para toda la comunidad que tenga en cuenta las necesidades actuales.

Últimas Fotos

Instalador de Apache Flex

Entrar o Registrase