Hace unos momentos, me encontraba respondiendo preguntas en Foros del Web (despues de Stack Overflow, mi comunidad favorita de programación) y surgió un tema en el donde un usuario preguntaba si era posible hacer un sistema de login en Flash.
Es posible, aunque yo no lo recomendaría porque para empezar Flash puede ser descompilado, y se puede ver las cadenas de información que se envían; además cualquier persona con un sniffer en la red podría ver la información en texto plano (nombre de usuario y contraseña), creo que existen mejores alternativas para realizar un sistema de login o un sistema de control de usuarios, mucho mas eficientes y mas sencillas de mantener.
Sin embargo, no soy quien para decidir lo que los programadores hagan con sus proyectos, por lo que solamente me limite a responder y proponer una implementación de autentificacion de usuarios con flash, php, mysql y XML.
El código presentado a continuación, no implementa buenas prácticas de programación y es mostrado solamente con fines informativos. No es un código que deba ser implementado en producción. Ver recomendaciones al final del post.
Asumo que las personas interesadas en este sistema, ya tienen un gestor de bases de datos instalados en sus servidores. En el ejemplo yo utilizaré MySQL (es lo más comun) y la estructura de mi tabla de usuarios es la siguiente:
[codesyntax lang="sql"]
CREATE TABLE IF NOT EXISTS `users` ( `agent_code` int(5) NOT NULL DEFAULT '0', `username` varchar(13) DEFAULT NULL, `password` varchar(32) NOT NULL DEFAULT '', `hash` varchar(32) NOT NULL DEFAULT '', PRIMARY KEY (`agent_code`), UNIQUE KEY `hash` (`hash`), UNIQUE KEY `username` (`username`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
[/codesyntax]
Utilizaremos la misma tecnica mostrada en este tutorial:
http://alanchavez.com/tutorial-de-como-conectar-flash-con-mysql-php-xml-hd
Es decir, vamos a crear un archivo XML al vuelo, un archivo XML que nunca existirá en nuestro servidor y será solamente utilizada para intercambiar información entre flash y php.
En la entrada anterior; un visitante preguntó porque utilizaba XML, si JSON era mas apropiado para esta labor. En si, ambos lenguajes son equivalentes de acuerdo a los criterios de Turing, ambos tienen ventajas y desventajas; en lo particular me inclino más hacia JSON que hacia XML, me parece que es mucho mas sencillo y fácil de entender.
Sin embargo Adobe Flash provee una integración nativa solo para XML, para utlizar JSON y Flash, es necesario instalar una librería externa llamada as3corelib ( https://github.com/mikechambers/as3corelib ) que la menciono en este post:
http://alanchavez.com/tutorial-como-tomar-fotos-con-una-camara-web-desde-flash-procesarla-con-php
Asi que para fines prácticos, decidí hacer el tutorial utilizando PHP y XML.
Mi archivo PHP luce de la siguiente manera:
[codesyntax lang="php"]
<?php
ob_start();
$hostname = "localhost";
$username = "usuario";
$password = "password123";
$database = "mibd";
$mysqli = new MySQLi($hostname, $username, $password, $database);
$amIConnected = ($mysqli->connect_errno) ? FALSE: TRUE;
isset($_POST['usr']) or die ("username not set");
isset($_POST['pwd']) or die ("password not set");
$usr = $_POST['usr'];
$pwd = md5($_POST['pwd']);
$strSQL = "SELECT `agent_code` FROM `users` WHERE `username` = ? AND `password` = ?";
if(!($query = $mysqli->prepare($strSQL))) {
die("Query no preparadanCausas:n".$mysqli->error);
}
if(!($query->bind_param("ss",$usr,$pwd))) {
die("Los parametros no fueron asociados con la consulta SQLnCausas:n".$query->error);
}
if(!($query->execute())) {
die("Hubo un error durante la ejecucion de la consulta SQL:n".$query->error);
}
if(!($query->bind_result($agent_code))) {
die("No se pudo asociar el resultado con la variable:n".$query->error);
}
if($query->fetch()) {
header("Content-Type: text/xml");
echo "<?xml version="1.0" encoding="utf-8"?>n";
echo "<authentication>n";
echo "<result>n";
echo "<ID>".$agent_code."</ID>n";
echo "</result>n";
echo "</authentication>";
} else {
header("Content-Type: text/xml");
echo "<?xml version="1.0" encoding="utf-8"?>n";
echo "<authentication>n";
echo "<result>n";
echo "<ID>-1</ID>n";
echo "</result>n";
echo "</authentication>";
}
$query->close();
$mysqli->close();
?>
[/codesyntax]
En esta implementación, hago uso del nuevo módulo MySQLi; sin embargo para tener una experiencia POO verdadera, es recomendable utilizar PDO.
La ventaja de utilizar MySQLi o PDO, sobre MySQL son las consultas preparadas (prepared statements). Que tienen de especial, se preguntarán?
Simple y sencillamente, evitan la inyección SQL (existe una vulnerabilidad cuando se piensa cambiar el CHARSET, sin embargo no es común ese escenario por lo que no será cubierta en este artículo). Lo que sucede, es que al utilizar prepared statements, se obliga al programador (y al interprete) a conocer el código SQL ANTES de que sea ejecutado, por lo que si un atacante intenta ingresar comandos SQL, el gestor literalmente comparará los intentos de inyeccion SQL contra los valores de las tablas, y NO serán ejecutados, ya que la consulta SQL YA HA SIDO INTERPRETADA… bastante ingenioso, no
?
Finalmente éste es el código en ActionScript 3:
[codesyntax lang="actionscript3"]
package {
import flash.display.*;
import flash.net.*;
import flash.events.*;
import flash.text.*;
public class main extends MovieClip {
private static const DEFAULT_TEXT:String = "Iniciando Test...";
private static const dx:int = 400;
private static const URL:String = "index.php";
private var _loader:URLLoader;
private var _request:URLRequest;
private var _requestVars:URLVariables;
public var usuario:TextField = new TextField();
public var contrasena:TextField = new TextField();
public var usuarioLbl:TextField = new TextField();
public var contrasenaLbl:TextField = new TextField();
public var botonLogin:MovieClip = new MovieClip();
public var campo:TextField = new TextField();
public function main() {
usuario.type = TextFieldType.INPUT;
usuario.background = true;
usuario.border = true;
usuario.height = 20;
usuario.x = 300;
usuario.y = 150;
addChild(usuario);
usuarioLbl.text = "Nombre de usuario:";
usuarioLbl.x = 170;
usuarioLbl.y = 150;
addChild(usuarioLbl);
contrasena.type = TextFieldType.INPUT;
contrasena.background = true;
contrasena.displayAsPassword = true;
contrasena.border = true;
contrasena.height = 20;
contrasena.x = 300;
contrasena.y = 200;
addChild(contrasena);
contrasenaLbl.text = "Contraseña:";
contrasenaLbl.x = 170;
contrasenaLbl.y = 200;
addChild(contrasenaLbl);
botonLogin.graphics.beginFill(0x0000FF);
botonLogin.graphics.drawRect(0,0,100,20);
botonLogin.graphics.endFill();
botonLogin.x = (contrasenaLbl.x + contrasenaLbl.width + contrasena.x + contrasena.width)/2-botonLogin.width;
botonLogin.y = 250;
botonLogin.addEventListener(MouseEvent.CLICK,autenticarUsuario);
addChild(botonLogin);
campo.border = true;
campo.width = 400;
campo.height= 100;
campo.x = botonLogin.x-150;
campo.y = 300;
campo.text = DEFAULT_TEXT;
addChild(campo);
}
public function autenticarUsuario(e:MouseEvent):void{
output("Validando Usuario...");
if((usuario.text=="")||(contrasena.text=="")) {
output("Campo de usuario o contrasena está vacíon");
} else {
_requestVars = new URLVariables();
_requestVars.usr = usuario.text;
_requestVars.pwd = contrasena.text;
_request = new URLRequest(URL);
_request.method = URLRequestMethod.POST;
_request.data = _requestVars;
_loader = new URLLoader();
_loader.addEventListener(Event.COMPLETE,completado);
_loader.load(_request);
}
}
public function output(s:String):void
{
campo.appendText(s+"n");
}
public function completado(e:Event)
{
output("Consulta completada!n");
output("Mostrando Infomación:n");
var xml:XML = new XML(e.target.data);
var campos:XMLList = xml..result;
if(campos[0].ID == -1) {
output("Usuario no existe");
} else {
output("Usuario autentificado con ID: "+campos[0].ID);
}
}
}
}
[/codesyntax]
Como podrán darse cuenta, el código es muy similar al del tutorial anterior.
El archivo PHP hace una consulta a la base de datos para saber si el usuario existe, si no existe se construye un archivo XML con un ID = -1; este valor es leído por Flash y le muestra al usuario que el usuario no existe en la base de datos. Por otro lado, si el usuario existe, se muestra su ID.
Como verán es un sistema muy simple, que puede resultar útil en algunas ocasiones. Sin embargo:
NO ES UNA IMPLEMENTACIÓN SEGURA. MIRA PORQUE:
El nombre de usuario y contrasena se envian en texto plano hacia PHP, asi que es vulnerable a un ataque de “man in the middle”, alguien escuchando el trafico de tu red, podrá ver los datos que estás enviando.
Cuando se trata de seguridad, realmente no existe una solución absoluta; simplemente se le adhieren capas de seguridad a las aplicaciones, entre mas capas hay; menos deseable es el objetivo y mas díficil es de penetrar el sistema. La manera en la que yo agregaría una capa extra de seguridad, sería utilizando un algoritmo de encriptacion, con un secreto, como SHA256. Existe una librería para encriptar textos en AS3, sin embargo está en estado experimental y no tiene muy buen desempeño; los resultados varían de computadora a computadora. Puedes descargarla de acá:
http://code.google.com/p/as3crypto/
SHA256 encriptará tu password con un secreto (una clase de contrasena) en PHP recibes la contrasena encriptada y la desencriptas con el mismo secreto.
Es mala practica de programación, guardar los secretos en la base de datos. No lo pongas en Flash en texto plano, porque Flash se puede desencriptar y van a ver tu secreto, por lo tanto no tiene caso tomarse la “molestia” de encriptar el password.
Yo lo pondria en el servidor, FUERA DEL WEBROOT, con permisos solamente de lectura para el usuario y grupo de apache, y lo leería de la siguiente forma:
- Abrir el archivo desde flash, y obtener el valor del secreto con expresiones regulares.
- Con el secreto dentro de flash; encriptar la contrasena del usuario, y pasar SOLAMENTE el usuario y contraseña al archivo de autentificacion.
Posteriormente en el script PHP donde se verifica si el usuario existe o no:
- Leer el archivo donde se encuentra el secreto, y obtener el valor del secreto con expresiones regulares.
- Desencriptar la contrasena del usuario
- Codificarla con MD5 (en realidad se recomienda PBKDF2, ya que MD5 ya no se considera seguro)
- Tratar de encontrar a un usuario, con esa contrasena.
Si creen que existe una mejor alternativa, o han desarrollado lo mismo de una manera segura, publiquenla en los comentarios y a compartir!
[download id="3"]
hola
hola