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
- File … Import … Web->War File
- WAR File: El camino del archivo blazeds.war
- Web Project: FlexSpringSecurity
- Target Runtime: Alguno que se haya configurado previamente
- 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
-
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;
-
-
public class User {
-
-
private Long id;
-
private String fullName;
-
private String userName;
-
private String password;
-
-
@Id
-
@GeneratedValue
-
return id;
-
}
-
-
this.id = id;
-
}
-
-
return fullName;
-
}
-
-
this.fullName = fullName;
-
}
-
-
return userName;
-
}
-
-
this.userName = userName;
-
}
-
-
return password;
-
}
-
-
this.password = password;
-
}
-
-
private List<Role> roleList;
-
-
@ManyToMany(cascade=CascadeType.ALL, fetch=FetchType.LAZY)
-
public List<Role> getRoleList() {
-
return roleList;
-
}
-
-
public void setRoleList(List<Role> roleList) {
-
this.roleList = roleList;
-
}
-
}
Role.java
-
package com.nonocarballo.flexspringsecurity.valueobject;
-
-
import javax.persistence.Entity;
-
import javax.persistence.GeneratedValue;
-
import javax.persistence.Id;
-
-
public class Role {
-
-
private Long id;
-
private String name;
-
-
@Id
-
@GeneratedValue
-
return id;
-
}
-
-
this.id = id;
-
}
-
-
return name;
-
}
-
-
this.name = name;
-
}
-
}
RoleType.java
IUserService.java
-
package com.nonocarballo.flexspringsecurity.service;
-
-
import java.util.List;
-
import com.nonocarballo.flexspringsecurity.valueobject.User;
-
-
public interface IUserService {
-
public List<User> loadAllUsers();
-
public void addUser(User user);
-
}
UserService.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;
-
-
@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<User> loadAllUsers() {
-
// TODO Auto-generated method stub
-
return entityManager.createQuery("from User u").getResultList();
-
}
-
-
@Override
-
@Transactional
-
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);
-
}
-
}
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
-
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<Role> roleList = new ArrayList<Role>();
-
-
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>();
-
-
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>();
-
-
role = new Role();
-
role.setName(RoleType.USER);
-
roleList.add(role);
-
-
user.setRoleList(roleList);
-
-
userService.addUser(user);
-
-
ArrayList<User> userList = (ArrayList<User>) userService.loadAllUsers();
-
-
assertTrue(userList.size() == 3);
-
-
}
-
}
Para la interacción entre spring-flex y Spring Security debe registrarse en el archivo web.xml el un filtro del tipo DelegatingFilterProxy.
-
<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>
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
-
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;
-
-
public class MyUserDetailsService implements UserDetailsService {
-
-
private IUserService userService;
-
-
public IUserService getUserService() {
-
return userService;
-
}
-
-
@Autowired
-
public void setUserService(IUserService userService) {
-
this.userService = userService;
-
}
-
-
@Override
-
@Transactional
-
throws UsernameNotFoundException, DataAccessException {
-
User user = userService.loabByUserName(arg0);
-
if(user == null){
-
throw new
-
UsernameNotFoundException("User "+arg0+" not found.");
-
}
-
List<Role> roleList=(List<Role>) user.getRoleList();
-
ArrayList<GrantedAuthority> grantedAuthorities = new
-
ArrayList<GrantedAuthority>();
-
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);
-
-
}
-
}
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:
-
<security:http entry-point-ref="entryPoint">
-
<security:anonymous enabled="false"/>
-
</security:http>
-
-
<bean id="entryPoint" class="org.springframework.security.web.authentication.Http403ForbiddenEntryPoint"/>
-
-
<security:authentication-manager>
-
<security:authentication-provider user-service-ref="myUserDetailsService"/>
-
</security:authentication-manager>
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.
-
<flex:message-broker>
-
<flex:secured />
-
</flex:message-broker>
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).
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 version="1.0" encoding="UTF-8"?>
-
<channels>
-
<channel type="amf">
-
<uri>http://localhost:8080/flexspringsecurity</uri>
-
</channel>
-
<channel type="secure-amf">
-
<uri>https://localhost:8080/flexspringsecurity</uri>
-
</channel>
-
</channels>
-
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);
-
}
-
}
-
}
Cuando presionamos el botón “Entrar” invocamos el método login del objeto ChannelSet con la credencial del usuario:
-
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));
-
}
-
}
-
}
Si la credencial de usuario no es válida obtendremos:
En caso contrario:
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
-
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;
-
-
-
return fullname;
-
}
-
-
private Collection<GrantedAuthority> authorities;
-
-
@Override
-
public Collection<GrantedAuthority> getAuthorities() {
-
// TODO Auto-generated method stub
-
return authorities;
-
}
-
-
@Override
-
// TODO Auto-generated method stub
-
return password;
-
}
-
-
@Override
-
// 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
-
}
-
-
Collection<GrantedAuthority> authorities) {
-
super();
-
this.username = username;
-
this.fullname = fullname;
-
this.password = password;
-
this.authorities = authorities;
-
}
-
}
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
-
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;
-
}
-
-
}
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.
-
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<String, Object> getAuthenticationResult() {
-
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
-
if (authentication == null) {
-
return null;
-
}
-
Map<String, Object> authenticationResult = new HashMap<String, Object>();
-
authenticationResult.put("name", authentication.getName());
-
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;
-
}
-
}
Finalmente debemos registrar nuestro interceptor de mensaje personalizado:
-
<flex:message-broker>
-
<flex:message-interceptor ref="myLoginMessageInterceptor"/>
-
<flex:secured />
-
</flex:message-broker>
Cuando nos autenticamos nuevamente obtenemos:
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.
Acerca de esta entrada
Usted está leyendo “Autenticación en aplicaciones Flex con Spring Security 3,” una entrada de MadeInFlex
- Autor: Nono F. Carballo Escalona
- Publicada:
- 13.08.10 / 1am
- Categorías:
- Artículos, Seguridad, Tutoriales
- Entradas relacionadas:
- BlazeDS más cerca de Spring
- Flash Player Security Update
- Clear Toolkit ahora con soporte para Spring
- Granite Data Services 1.0.0
- Número de visitas:
- 5351




9 Comentarios
Ir al formulario de comentarios | rss (comentarios) [?] | trackback url [?]