Comunidad de diseño web y desarrollo en internet

Sesiones en Flash Remoting, PHP y AMFPHP

Este tutorial mostrara lo sencillo y practico que es usar sesiones php en nuestros swf 's a través de AMFPHP , para de este modo disponer de información en el espacio de memoria del servidor ( session php ), en lugar de tener toda la información en el espacio del cliente (nuestro . swf ), con esto conseguimos hacer opaca dicha información al cliente, permitiéndonos contrastar datos importantes de una manera segura.

Para ello voy a usar un sistema de login simple, que permitirá autorizarse como usuario registrado, o bien registrarse como usuario nuevo, mostrándonos al acceder toda la información que contiene la sesión.

Requerimientos para seguir el tutorial

Ok , vamos a empezar primero por diseñar nuestros servicios PHP , que se incorporarán al AMFPHP , en estos php 's definiremos como accederemos a la sesión y a los datos de MySQL .

Servicios en PHP

Para nuestro ejemplo usaremos varios ficheros php's:

config.php

<?php 
 define('DB_SERVER', 'localhost'); 
 define('DB_SERVER_USERNAME', 'root'); 
 define('DB_SERVER_PASSWORD', ''); 
 define('DB_DATABASE', 'tutosessdb'); 
 ?> 

Este fichero define las opciones de configuración de nuestros servicios, datos como la url del servidor de base de datos con el que queremos conectar, el usuario de BD que usaremos, su password, y el nombre de la base de datos especifica a usar.

tablas.php

<?php 
 define('TABLA_USERS','usuarios'); 
 ?> 

Este fichero define un alias, para nuestras tablas, aunque en este ejemplo solo disponemos de una, en tu proyecto puedes necesitar más, y es una forma de parametrizar nuestro código, pues si cambiamos el nombre de una tabla, no habrá que cambiar todas las referencias a la misma dentro del código, sino que lo haremos solo aquí, y ya :)

incl.php

<?php  
// Valida el contenido de una variable 
function not_null($vAL) { if (is_array($vAL)) { if (sizeof($vAL) > 0) return true; else return false; } else { if (($vAL != '') && (strtolower($vAL) != 'null') && (strlen(trim($vAL)) > 0)) return true; else return false; } } // Valida un password en texto plano contra uno encriptado
function pass_validate($pLN, $eNCR) { if (not_null($pLN) && not_null($eNCR))
{
$pIL = explode(':', $eNCR);
if (sizeof($pIL) != 2)
return false;
if (md5($pIL[1] . $pLN) == $pIL[0])
return true; } return false; } // Genera un pass encriptado a partir de uno texto plano
function pass_encript($pLN) { $pASS = ''; for ($i=0; $i<10; $i++) $pASS .= mt_rand();
$sALT = substr(md5($pASS), 0, 2); $pASS = md5($sALT . $pLN) . ':' . $sALT;
return $pASS; } //Obtenemos la ip real del cliente function get_ip() { if (preg_match('/^(\d{1,3}\.){3}\d{1,3}$/s', $_SERVER["HTTP_CLIENT_IP"]))
$ip = $_SERVER["HTTP_CLIENT_IP"];
else { if (preg_match('/^(\d{1,3}\.){3}\d{1,3}$/s', $_SERVER["HTTP_X_FORWARDED_FOR"]))
$ip = $_SERVER["HTTP_X_FORWARDED_FOR"];
else
if (preg_match('/^(\d{1,3}\.){3}\d{1,3}$/s', $_SERVER["REMOTE_HOST"]))
$ip = $_SERVER["REMOTE_HOST"];
else
$ip = $_SERVER["REMOTE_ADDR"]; } return($ip);
}
?>

 

Bueno este último include, es un poco más elaborado, pero no es más que una pequeña libreria de funciones, que usaremos en nuestros servicio users.php para obtener la ip de nuestro usuario, y validar/encriptar su clave.

users.php

<?php  
include ("include/config.php");  
include ("include/tablas.php");  
include ("incl.php");

class users 
  { 

  //Constructor de clase 
  function users() 
  { 
  //Aquí definimos la tabla de metodos exportables a Flash. 
  $this->methodTable = array( 
					"regUser" => array( 
					"description" => "Registra un nuevo usuario", 
"access" => "remote", "arguments" => array ("user","pass","email") ),
"loGin" => array( "description" => "Logea un usuario", "access" => "remote",
"arguments" => array ("user","pass")
),
"loGout" => array( "description" => "Cierra la sessión de un usuario",
"access" => "remote" )
);
//Conectamos con nuestro servidor de BD, con los datos que tenemos definidos en config.php
mysql_connect(DB_SERVER, DB_SERVER_USERNAME, DB_SERVER_PASSWORD);
//Seleccionamo la base de datos de trabajo, con el dato definido igualmente en la config.php
mysql_select_db(DB_DATABASE);
} //Registra un nuevo usuario en el sistema function regUser($user,$pass,$email) { //Codigo de Registro $pass = pass_encript($pass); //Encriptamos el "pass" recibido de Flash //Ahora comprobamos si el nombre de usuario ya existe en la BD, //en caso afirmativo, devolveremos un código de error a Flash informando de, //esta incidencia, ahora bien, si no existe, lo añade sin problemas. if (!$this->extUser($user)) {
//Con esta instrucción almacenamos en la BD los datos del nuevo usuario, si la función devuelve "true"
//significa que la operación de almacenamiento tuvo exito, y por tanto ya se añadió nuestro usuario
//en caso contrario, se devuelve a Flash, otro código de error informando de esta incidencia.
if(mysql_query("insert into " . TABLA_USERS . " (user,pass,email,alta) values ('". addslashes($user)."','".addslashes($pass).
','".addslashes($email)."','".
addslashes(date("Ymd"))."')"))
{
//Una vez el usuario ha sido registrado correctamente en la BD,
//guardamos en la session el usuario y la password encriptada

$_SESSION['ulogged'] = $user; $_SESSION['plogged'] = $pass;
$_SESSION['iplogged'] = get_ip();
return 1;//Registro efectuado
}
else
return 0;//Registro fallido
}
return 2; //Error el usuario ya existe
} //Loggea a un usuario en el sistema, autorizandolo.
function loGin($user,$pass) { //Codigo de Login //Con esta instrucción consultamos a la BD los datos de que dispone del usuario solicitado $rst = mysql_query("select * from " . TABLA_USERS . " where user='" . addslashes($user) ."'");
//si el recuento de registro es distinto de 0, significa que el usuario existe en la
//BD, y aprovechamos la consulta realizada, para obtener sus datos, y autorizarle.

if(mysql_num_rows($rst)!=0)
{
$row = mysql_fetch_array($rst);//Extraemos los datos
//Obtenemos la ip real del usuario.
$ip = get_ip(); ///---- Procedemos a realizar el login con estos datos. //--------------------------------------------------------------- //Esta instrucción está declarada en incl.php, y devuelve "true" si el password pasado es válido. if(pass_validate($pass,$row['pass']))//Validamos el password { //Si Flash nos pasó un password en texto plano "CORRECTO", pues //damos por bueno el login, e informamos a Flash de ello, para que obre en consecuencia :)
$_SESSION['ulogged'] = $user; //Salvamos en la sesion el usuario
$_SESSION['plogged'] = $row['pass']; //la pass encriptada
$_SESSION['iplogged'] = $ip;// y la IP del cliente.
return 1; //login correcto } else return 2; //password incorrecto } else return 0; //El usuario no existe } //Expulsa del sistema al usuario
function loGout() { session_destroy(); //Destruimos la sessión. return true; } //Esta es una función de uso interno, no se publica hacia Flash, es simplemente para saber si un usuario
//ya existe en la BD.
function extUser($user) { return intval(mysql_result(mysql_query("select count(*) as existe from "
. TABLA_USERS . " where user='"
. addslashes($user) ."'"),0,"existe"));
}
} ?>

Este es el servicio users.php para AMFPHP, está diseñado para mostrar la comunicación con sesiones desde Flash, por lo tanto no es un servicio funcional, para producción, ya que todo el cotejo de los datos de session contra los de flash, para dar autenticidad real al login no ha sido implementado en este ejemplo.

sessions.php

<?php 
class sessions{
	function sessions()  
	{ 
$this->methodTable = array(
"write" => array(
"description" => "Guarda el valor 'value' en la variable de session 'key'.", "access" => "remote",
"arguments" => array ("key","value") ), "read" => array( "description" => "Devuelve el contenido de la variable de session 'key'.",
"access" => "remote",
"arguments" => array ("key") ),
"readAll" => array(
"description" => "Devuelve el contenido de la session completa",
"access" => "remote"
),
"sessid" => array(
"description" => "Devuelve el Id de session activa.",
"access" => "remote"
),
"erase" => array( "description" => "Elimina la variable 'key' de la session",
"access" => "remote",
"arguments" => array("key")
)
); }

//Escribe un valor desde Flash en la sessión activa
function write($key, $value) { $_SESSION[$key] = $value; return isset($_SESSION[$key]); }
//Devuelve a Flash el contenido de una variable de sessión concreto function read($key) { return $_SESSION[$key]; }
//Devuelve a Flash el contenido completo de la sessión. function readAll() { return $_SESSION; } //Devuelve a Flash el identificador de la sessión activa function sessid() { return session_id(); }
//Elimina una varible de sessión desde Flash. function erase($key) { unset($_SESSION[$key]); return true; }
} ?>

Este es nuestro servicio sessions.php, y como observaréis es bastante simple, dado que desde la versión 1.0 MS3 de AMFPHP hay soporte a sesiones php de manera nativa por lo tanto vuestras sesiones se iniciaran automáticamente, nada mas conectar con AMF, y contendrán siempre una variable de control llamada amfphp_session_inited con el valor true esta variable de session la genera el propio AMF por defecto, de modo que nosotros en nuestro servicio, daremos por hecho que la sesión está iniciada, y por lo tanto la usaremos directamente.

La comunicación por remoting tiene la peculiaridad de que trabajaremos contra webservices como los que acabamos de crear, y esto implica una comunicación asimétrica, es decir, en Flash debes hacer una llamada al servicio, y esperar en otra función la respuesta, por lo tanto vamos a buscar formas de agilizar las comunicaciones cuando diseñemos nuestros webservices, es por este motivo que defino la función readAll dentro del servicio, para que desde Flash podamos manejar todo el contenido completo de la sesión actual en php con una sola solicitud (imagina que tuvieras muchas variables).

¿Entendiste todo hasta aquí? Como podréis ver es bien sencillo de utilizar, la gracia de esto viene, por el hecho de que el uso de sesiones php desde Flash, nos ofrece la ventaja de poder disponer de un espacio de memoria para nuestro usuario en el servidor, espacio de memoria que tiene un tiempo de vida, y un identificador único y complejo, con esto dejamos de estar "vendidos" a un de-compilado de nuestro flash, en operaciones que requieran cierta seguridad real, dado que nuestro flash, como veremos ahora, solo contendrá llamadas a nuestros recién creados servicios, y por tanto trabajaremos desde Flash parametrizado con los valores recogidos de la sesión, sin que nuestro "cliente" Flash conozca como se obtienen los datos, ni de donde vienen, y por tanto disponemos de una mayor seguridad. Lo ideal sería que nuestro Flash sirva simplemente de interfase, para con nuestro sistema de servidor, que es opaco al cliente.

Otro tema importante es, que hacemos en flash cuando nuestro servicio nos devuelve "ok", pues desde luego no debemos poner un booleano con un bonito "true" dentro y con eso tenemos acceso a todo, pues eso sería fácilmente evitable al poderse descompilar nuestro Flash, y nos tiraría por tierra todo nuestro sistema. En lugar de eso, lo que debemos hacer es guardar en una variable, los datos significativos de nuestro usuario, estos datos no son conocidos ni por el usuario, ni por el mismísimo Flash, sino que son recogidos de la sesión, los datos a guardar serían: user, pass encriptada, sessid, con esto podemos cotejar siempre cualquier nuevo servicio que consideremos "critico" en nuestros webservices, garantizándonos así que los datos son reales, y no alterables dado que no existen en el código de programación de nuestro flash.

Código de Flash

Bien, para conectarnos a nuestros servicios, vamos a utilizar una clase de flash para cada uno de ellos, si si un .as, así es mejor, pues encapsulas todo en un objeto, y luego en tu .fla de aplicación, solo has de instanciar una variable de tipo user o tipo session y usar desde ella los servicios, de todos modos, recomiendo encarecidamente que uséis la clase que ha desarrollado nuestro compañero Dano, y que Maikel mostró en su Tutorial Manejar AMFPHP facil con una clase en Actionscript . Yo para este ejemplo usé dos clases simples basadas en la plantilla que te genera el propio AMFPHP.

Users.as

import mx.services.Log; 
import mx.rpc.RelayResponder;  
import mx.rpc.FaultEvent;  
import mx.rpc.ResultEvent; 
import mx.remoting.Service;  
import mx.remoting.PendingCall;  
import mx.remoting.debug.NetDebug; 
class User { //Constantes ------------------------------------------------------ //Tipos de CallBack public static var REGISTER = 0; public static var LOGIN = 1; public static var LOGOUT = 2;
//Variables ------------------------------------------------------ public var onRegUser:Function; public var onLoGin:Function; public var onLoGout:Function; private var service:Service; private var loG:Log; //Constructor ---------------------------------------------------- function User() { loG = new Log(Log.DEBUG, "logger");
loG.onLog = function(message:String):Void { trace("AMF --> " + message); } NetDebug.initialize(); service = new Service("http://usuarios.lycoz.es/morphgx/remoting/gateway.php", loG, "users", null, null); }
//Llamadas a las Funciones del Servicio ---------------------------------------------- //Llama al servicio regUser de nuestro users.php function regUser(user:String, pass:String, email:String):Void {
var pc:PendingCall = service.regUser(user, pass, email);
pc.responder = new RelayResponder(this, "_onRegUser", "_onAmfError");
}
//Logea un usuario en el sistema
function loGin(user:String, pass:String):Void
{
var pc:PendingCall = service.loGin(user, pass);
pc.responder = new RelayResponder(this, "_onLoGin", "_onAmfError");
}
//Expulsa al usuario
function loGout():Void {
var pc:PendingCall = service.loGout();
pc.responder = new RelayResponder(this, "_onLoGout", "_onAmfError");
}
//------------------------------------------------------------------ //Función de CallBack ------------------------------------------------
function callBack (fc:Number, re:ResultEvent):Void
{
switch(fc)
{
case REGISTER:
if(onRegUser != null) onRegUser(re);
else
{
trace("");
trace("Función 'onRegUser' no declarada");
} break;
case LOGIN: if(onLoGin != null)
onLoGin(re);
else
{
trace("");
trace("Función 'onLoGin' no declarada");
}
break;
case LOGOUT: if(onLoGout != null)
onLoGout(re);
else
{
trace("");
trace("Función 'onLoGout' no declarada");
}
break;
} }

//Manejadores de las respuesta de Remoting
private function _onRegUser(re:ResultEvent):Void {
callBack(REGISTER,re);
}
private function _onLoGin(re:ResultEvent):Void
{
callBack(LOGIN,re);
}
private function _onLoGout(re:ResultEvent):Void
{
callBack(LOGOUT,re);
} //Manejo de los errores de Remoting
private function _onAmfError( err:FaultEvent ):Void
{
NetDebug.trace({level:"None", message:"Error: " + err.fault.faultstring });
}
}

Como podeis observar, es una clase sencilla, se limita a abstraer todo el servicio users.php, para ello inicia la comunicación con AMF en el constructor, y presenta un juego de funciones propias que llamaran mediante PendingCall a las funciones REALES en php, y para gestionar las respuesta y permitir que el usuario (osea nuestro .fla) pueda decidir que hacer con la respuesta (defininir la función de tratamiento de la respuesta en cada caso) pues implementa también una función intermedia de CallBack, de modo que la clase dirige el flujo de datos a otras funciones propias para tratar las respuesta, que lo único que hacen es retransmitir el objeto ResultEvent, hacia la función de CallBack, y ésta lo retransmite hacia la función correspondiente definida fuera (en el .fla)

Session.as

Esta Clase es IDENTICA a la anterior expuesta, solo que para el servicio sessions.php, por lo tanto os insto a que la escribáis vosotros, basándose en la clase anterior, y en el php, sessions.php, es muy sencillo ya vereis, si hay dudas, en el foro se resolverán ;)

Nuestro .fla de Login

Montaremos nuestro .fla, creando los 2 formularios necesarios y el espacio de bienvenida, para ello nuestra película tendrá 3 fotogramas.

En el primero montaremos el login, debe quedar más o menos así:

En el segundo montaremos el formulario de registro:

Y en el tercero, daremos la bienvenida a nuestro usuario autorizado, mostrando sus datos de sesión:

Bien, ahora pasaremos al código que lleva eso ;)

Crearemos una capa por encima de las de los 3 fotogramas estos:

en ella introduciremos este código

import mx.rpc.ResultEvent; 
import User; //Importamos la Clase: User.as 
import Session; //Importamos la Clase: Session.as 
   
var ssid:String; //Esta variable contendrá el id de session var 
var svSess:Session = new Session(); //Instanciamos el objeto session 
var svUser:User = new User(); //y el objeto usuario 
   
//CREAREMOS UN MANEJADOR PARA EL LOGIN 
//Y LO REGISTRAMOS EN EL OBJETO USUARIO 
svUser.onLoGin = function (msg:ResultEvent) 
{ 
	lnmsg.text=""; 
	switch(msg.result) 
	{ 
		case 0: //No existe el usuario 
			pass.text = ""; 
			gotoAndStop("registro"); 
			break;    
		case 1: //Login correcto 
			gotoAndStop("wellcome"); 
			break; 
		case 2: //Password invalida 
			pass.text = ""; 
			lnmsg.text = "Password Incorrecta."; 
			break; 
		} 
	}; 
   
//CREAREMOS UN MANEJADOR PARA EL REGISTRO 
//Y LO REGISTRAMOS EN EL OBJETO USUARIO 
svUser.onRegUser = function (msg:ResultEvent) 
{ 
	lnmsg.text=""; 
	switch(msg.result) 
	{ 
		case 0: //No existe el usuario 
			lnmsg.text="Error de Escritura."; 
			break; 
		case 1: //Login correcto 
			gotoAndStop("wellcome"); 
			break; 
		case 2: //Password invalida 
			user.text = ""; 
			pass.text = ""; 
			lnmsg.text = "El usuario ya existe."; 
			break; 
			} 
	}; 
 
//CREAREMOS UN MANEJADOR PARA EL LOGOUT 
//Y LO REGISTRAMOS EN EL OBJETO USUARIO 
svUser.onLoGout = function (msg:ResultEvent) 
{ 
	if(msg.result) 
		gotoAndStop("login"); 
	}; 

Con esto lo que hacemos es declarar e instanciar un solo objeto Session y User, y definimos las funciones de aplicación para las respuestas para toda la película, de modo que ahora en cada fotograma de formulario solo tendremos las correspondientes llamadas, simplemente...

Así,

Fotograma 1:

import mx.rpc.ResultEvent; 
stop(); 
//Solicitamos el identificador de session 
//nada más entrar, para poder usarlo en toda la pelicula 
svSess.sessid(); 
//Definimos la función de tratamiento, de modo que el id recibido 
//sea almacenado en ssid, que es una variable tipo String que se encuentra 
//en la capa 3 
svSess.onSessid = function (mq:ResultEvent) 
{ 
	ssid = mq.result; 
	}; 
////////// FUNCIONES DE APLICACIÓN 
ok.onRelease = function() 
{ 
	//Lanzamos el servicio 
	svUser.loGin(user.text,pass.text); 
	};

Aquí como vereis, a parte de la llamada al servicio de login, solicitamos también el identificador de sesión que se nos ha asignado, llamando a su servicio correspondiente

Fotograma 2:

stop();
////////// FUNCIONES DE APLICACIÓN 
ok.onRelease = function() {
	svUser.regUser(user.text, pass.text, email.text);
	};

Bueno aquí simplemente lanzamos el servicio "registro"

Fotograma 3:

import mx.rpc.ResultEvent; 
stop();
ssd.text = ssid; //Mostramos el id de session 
//Solicitamos el contenido completo de variables de session 
svSess.readAll();


////////// FUNCIONES DE APLICACIÓN 
off.onRelease = function() 
{ 
	svUser.loGout(); 
	} 
   
//Definimos la función de tratamiento de la respuesta 
//para que hacer con los datos recibidos desde php 
svSess.onReadAll = function (m:ResultEvent) 
{ 
	SESSION = m.result; //Asignamos a SESSION el resultado obtenido 
	wellcome.text = SESSION['ulogged']; //Ahora vamos mostrando cada dato 
	pss.text = SESSION['plogged']; //asignando las variables que segun el servicio 
	ipu.text = SESSION['iplogged']; //son declaradas dentro de la session, a cada 
	//campo de texto. 
	}; 

En este fotograma como bien dice los comentarios, nos dedicamos a leer en nuestro .fla el contenido del array SESSION que contiene todos los datos actuales del array $_SESSION de PHP. :)

Conclusiones

Espero que os sirva de ayuda, y que entendáis que al contrario de lo que se pensaba, el uso de sesiones php en flash si que es útil y necesario :)

¿Sabes SQL? ¿No-SQL? Aprende MySQL, PostgreSQL, MongoDB, Redis y más con el Curso Profesional de Bases de Datos que empieza el martes, en vivo.

Publica tu comentario

El autor de este artículo ha cerrado los comentarios. Si tienes preguntas o comentarios, puedes hacerlos en el foro

Entra al foro y participa en la discusión

o puedes...

¿Estás registrado en Cristalab y quieres
publicar tu URL y avatar?

¿No estás registrado aún pero quieres hacerlo antes de publicar tu comentario?

Registrate