Forums Développement Multimédia

Les formations Mediabox
Les formations Mediabox



Authentification avec Flex et Zend (1/2) - Côté Flex

Compatible Flex Builder 3. Cliquer pour en savoir plus sur les compatibilités.Compatible ActionScript 3. Cliquer pour en savoir plus sur les compatibilités.Compatible PHP. Cliquer pour en savoir plus sur les compatibilités.Par slyc3 (Steve Domin), le 09 mai 2009

Cette page est la première partie de ce tutoriel, un exemple complet d'authentification avec Flex et Zend AMF.

Il s'agit d'une traduction de Build a better Login with Adobe Flex, Zend_Amf, Zend_Auth, and Zend_Acl - On The Flex Side , de Keith Craigo, placé sous license Creative Commons Attribution-Share Alike 3.0 United States License.

La deuxième partie se trouve ici.

Traduction

Depuis qu'Adobe à annoncer qu'ils supportaient officiellement Zend_Amf, développé par Wade Arnold, j'ai pensé qu'il serait intéressant de se pencher dessus, j'ai donc développé un projet simpel de contrôle d'accès pour me familiariser avec le framework Zend.

Cette exemple est une application Flex 3 utilisant Cairngorm, Zend_Amf, Zend_Auth, et Zend_Acl pour vérifier les comptes utilisateurs et les permissions à l'aide d'une base de donnée MySQL.

Le code PHP de ce projet est couvert dans la seconde partie de ce tutoriel intitulé Authentification avec Flex et Zend - Côté PHP.

Zend_Auth permet de s'authentifier avec plusieurs adaptateurs, ainsi on trouve des adaptateurs pour OpenID, LDAP, fichiers textes et base de données. Pour ce tutoriel nous allons utiliser Zend_Db pour authentifier nos utilisateurs avec une base de donnée.

Zend_Acl sera utilisé pour contrôler les privilèges des utilisateurs.

Avec seulement quelques changements vous serez en mesure d'utiliser SQL Server, PostGreSQL, Oracle ou n'importe quelle autre SGB. De même si vous ne souhaitez pas utiliser MAMP vous aurez à modifier la configuration pour que celle-ci corresponde à celle de votre serveur web.
Ce tutoriel est spécifique à MAMP en local.


A noter: C'est un exemple ultra simplifié et ne se veut pas être une référence en matière de sécurité. Ce tutoriel ne rentre pas dans les détails du fonctionnement du framework Cairngorm, si vous avez besoin d'informations supplémentaires sur ce framework je vous invite à consulter la doc de Cairngorm. De même, consultez le guide de référence du framework Zend pour plus d'informations sur Zend_Amf, Zend_Auth et Zend_Acl.

Cette application est licencié sous :



Build a better Login with Flex, Zend_Amf, Zend_Auth, and Zend_Acl by Keith Craigo is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.


Vous pouvez voir l'application complète ici , effectuer un clic droit sur l'application pour regarder et télécharger le code Flex, SQL et PHP ou vous pouvez créer votre propre application au fur et à mesure de ce tutoriel.

Très bien, commençons :

Requis

  1. Flex Builder 3 ou le SDK gratuit et Open Source Flex
  2. Cairngorm
  3. PHP 5.2.8
  4. MAMP
  5. Zend Framework 1.7.1


Vue d'ensemble du projet

Commençons par clarifier la terminologie que nous allons employer, l'authentification et autorisation sont deux choses totalement différentes.
L'authentification consiste à vérifier que vous êtes qui vous prétendez être.
L'autorisation consiste à déterminer ce à quoi vous avez accès. Quels sont les privilèges qui vous sont assignés.
Le fait de pouvoir m'authentifier en tant que Keith ne signifie pas pour autant que je suis autorisé à visionner toutes les sections d'un site internet.
Voilà pour les bases.


Le premier écran de l'application demande à l'utilisateur de s'authentifier ou d'aller sur la partie publique, après la demande de connexion il y a trois cas possibles :

  1. L'utilisateur est effectivement authentifié et autorisé, ses privilèges d'accès vont être récupéré en fonction du rôle qui lui a été assigné : Super, admin ou manager. Ceci déterminera ce qu'il verra sur le panneau de contrôle.
  2. Si l'identifiant ou le mot de passe se trouve être incorrect l'utilisateur en sera alerté par le message suivant : ” Désolé ! Votre identifiant ou votre mot de passe est incorrect, essayez encore ou visitez la partie public du site. ”
  3. L'utilisateur n'est pas autorisé, comprenez qu'il n'a pas de rôle particulier, on lui donne donc par défaut le rôle “invité”, ce qui lui permettra uniquement de visionner la partie publique.


Configurer l'Acl peut vite devenir assez compliqué mais je vais faire en sorte que ça reste simple pour l'instant.

  • invité est le rôle par défaut et permet seulement d'accéder à la partie publique du site.
  • manager hérite de tous les privilèges d'invité plus quelques privilèges uniques que l'on peut voir dans le tableau
  • admin hérite de tous les privilèges d'invité et de manager plus quelques privilèges uniques que l'on peut voir dans le tableau
  • Super est un rôle spécial, il n'hérite d'aucun autre rôle mais à tous les privilèges plus certains autres privilèges uniques que l'on peut voir dans le tableau, j'en expliquerai plus dans la partie serveur du code.


Configuration initiale

Mise en place du répertoire de base

  1. Suivre les instructions de téléchargement et d'installation des composants 1 à 4.
  2. Télécharger et dézipper les fichiers du framework Zend 1.7.1 à un emplacement sur votre ordinateur.
  3. Créer deux nouveaux dossiers, frameworks et utils dans le dossier MAMP, le dossier parent de htdocs.
  4. Copier le dossier Zend Framework 1.7.1/library/Zend dans le dossier frameworks que vous venez juste de créer.


Voilà à quoi devrais ressembler la structure de votre projet :

  • MAMP/frameworks/Zend
  • MAMP/utils

Mise en place de la base de données MySQL de MAMP

Créer une nouvelle base et nommez la AccessControlExample.

Copier le code SQL suivant dans la table nouvellement créée :

CREATE TABLE IF NOT EXISTS `admin` (`username` varchar(20)NOT NULL,`password` varchar(20) NOT NULL,`realfirstName`
varchar(20) NOT NULL,`reallastName`
varchar(20) NOT NULL,`role` varchar(20)NOT NULL,PRIMARY KEY  (`username`))
ENGINE=InnoDB DEFAULT CHARSET=latin1;
INSERT INTO `admin`VALUES(’admin’, ‘password’,'admin’, ‘admin’, ‘admin’);
INSERT INTO `admin`
VALUES(’manager’, ‘password’,'manager’, ‘manager’,  ‘manager’);
INSERT INTO `admin`VALUES(’Super’, ‘password’,'Super’, ‘Super’, ‘Super’);


Côté Flex

Création du projet dans Flex Builder

Dans Flex Builder créer un nouveau projet Flex soit en faisant File→New→Flex Project ou alors en cliquant sur la flèche du bouton New Project et de sélectionner Flex Project comme illustré sur la photo suivante :


Dans la fenêtre suivante :


Pour le nom de projet, taper AccessControlExample.

Vous pouvez au choix créer le projet dans le répertoire par défaut ou spécifier une autre location.

Application Type - choisir Web Application (runs in Flash Player)
Server technology - choisir PHP pour Application Server Type

Cliquer sur suivant :


La configuration au dessus est assez explicite. MAMP tourne sur le 8888.
Votre configuration peut-être un peu différente en fonction de votre serveur Web.

Après avoir cliqué sur Validate Configuration et si tout se passe bien, cliquez sur Next puis sur l'onglet Library Path.


Ici vous pouvez changer le nom du fichier principal de votre application, personnellement j'ai choisi de laisser celui par défaut.

Cliquer sur Add SWC.

Indiquer le chemin jusqu'au fichier Cairngorm.swc que vous avez télécharger précédemment.

Cliquer sur Finish.

Configuration du Compiler


Créer un nouveau fichier de configuration dans à la racine de votre projet (dossier root), nommé services-config.xml.
Vous pouvez en fait sauvegarder ce fichier ou vous voulez, la seule condition étant de fournir ensuite la chemin absolu jusqu'à sa location.

Copier et sauver le xml suivant dans le fichier services-config.xml.

<?xml version=”1.0″ encoding=”UTF-8″?><services-config><services><service id=”amfphp-flashremoting-service”
class=”flex.messaging.services.RemotingService”
 
                            messageTypes=”flex.messaging.messages.RemotingMessage”><destination id=”zend”><channels><channel ref=”my-zend”/></channels><properties><source>*</source></properties></destination></service></services><channels><channel-definitionid=”my-zend”class=”mx.messaging.channels.AMFChannel”><endpoint uri=”http://localhost:8888/AccessControlExample/”
class=”flex.messaging.endpoints.AMFEndpoint”/></channel-definition></channels>
    </services-config>

Sélectionner Project→properties dans le menu principal de Flex Builder.

Dans la liste de gauche sélectionner Flex Compiler comme dans l'image suivante :


NOTE: voici ma configuration, la votre peut être différente en fonction de votre lieu.

Dans les arguments additionnels du compiler, après -locale en_US,
Ajoutez -services services-config.xml

Cliquer sur Apply si vous souhaitez effectuer les changements et en faire d'autre sinon cliquer simplement sur OK, ceci appliquera les changements et fermera la fenêtre.

Mise en place de la structure du projet

Dans le dossier src, créer la structure suivante :

  1. com/business
    1. Dans le dossier com/business créer les quatre dossier suivants : commands, control, events et delegates.
  2. com/vo
  3. com/views
  4. com/model

(Il y a d'autres options de structuration mais j'ai voulu rester aussi simple que possible pour l'instant).
Voilà à quoi devrais ressembler la structure de votre dossier src maintenant :


Dans le dossier business créer un nouveau composant nommé Services.mxml, acceptez les paramètres par défaut pour l'instant, copiez le code suivant dans ce fichier, puis sauvegarder et fermer len nous n'en aurons plus besoin dans ce tutoriel :

<?xml version=”1.0″ encoding=”utf-8″?><rds:ServiceLocatorxmlns:mx=”http://www.adobe.com/2006/mxml”
xmlns:rds=”com.adobe.cairngorm.business.*”>
    <mx:RemoteObjectid=”LoginService”
 
                                        destination=”zend”
 
                                        source=”Login” 
                                        showBusyCursor=”true”><mx:method name=”verifyUser”/></mx:RemoteObject>
</rds:ServiceLocator>

Dans le dossier com/model créer une nouvelle classe Actionscript nommé ModelLocator.
Laisser le champ Superclass vide, dans la section Interfaces cliquer sur le bouton Add et sélectionner IModelLocator ou commencer à taper IModel puis sélectionner com.adobe.cairngorm.model.IModelLocator.

Vous pouvez laisser le reste par défaut pour l'instant.

Après la déclaration :

package com.model
{

remplacer tout le code existant par celui-ci :

import com.adobe.cairngorm.CairngormError;

import com.adobe.cairngorm.CairngormMessageCodes;

import com.adobe.cairngorm.model.IModelLocator;
[Bindable]public class ModelLocator implements IModelLocator 
{// Single Instance of Our ModelLocator

    private static var __instance:ModelLocator = null;
 
    public function ModelLocator(__enforcer:SingletonEnforcer) 
    {
        if (__enforcer == null) 
        {throw new CairngormError( CairngormMessageCodes.SINGLETON_EXCEPTION, “ModelLocator” );
        
}
    }// Returns the Single Instance

 
    public static function getInstance() : ModelLocator 
    {if (__instance == null) 
        {
            
__instance = new ModelLocator( new SingletonEnforcer );

        }return __instance;
    
}

 
//DEFINE YOUR VARIABLES HEREpublic var workflowState:uint = LOGIN;

public var CURRENT_USER_ROLE:String= "";

public var MAINNAV:ArrayCollection = new ArrayCollection();
public var admingranted:Boolean = false;

public var supergranted:Boolean = false; 
 
// DEFINE VIEW CONSTANTS
public static const LOGIN:uint = 0;

public static const CONTROLPANEL:uint = 1;

public static const PUBLIC:uint = 2;
 
}// Utility Class to Deny Access to Constructor

class SingletonEnforcer {}

Ici nous avons défini notre écran par défaut, l'écran Login, dans la variable workflowState.
Un peu plus tard nous verrons comment changer cette variable pour contrôler les différents états de l'application.
Vous pouvez maintenant fermer le fichier ModelLocator.as, nous n'aurons plus besoin de ce code pour la suite.


Dans le dossier com/views créer les composants MXML (MXML Components) suivants. Tous sont basés sur le composant Canvas :

Login.mxml, PublicView.mxml et ControlPanel.mxml

Dans le fichier Login.mxml créer un formulaire contenant deux TextInput, le premier aura pour id “txt_username” , et le second “txt_password”. Ajoutez un FormHeading avec le label suivant : “Please Login or you can click Public in the nav bar above if you don’t have an account”. Vous pouvez aussi mettre en place une fonctionnalité permettant de s'inscrire mais c'est au-delà des objectifs de ce tutoriel.

Ajouter un bouton à ce formulaire avec pour id “btn_login” et comme label “LOGIN” et appeler la fonction verifyUser(event) pour l'évènement mouseClick, comme montré dans le code ci-après.

Sinon vous pouvez juste copier/coller le code suivant entre les balises <mx:Application>.

<mx:Script><![CDATA[
import com.model.ModelLocator;

import com.business.events.LoginEvent;

import flash.events.Event;

import com.vo.LoginVO;
 
[Bindable] 
public var __model:ModelLocator = ModelLocator.getInstance();
 
public function verifyUser(e:MouseEvent):void 
{var loginEvent:LoginEvent = new LoginEvent(new LoginVO(txt_username.text, txt_password.text));

    loginEvent.dispatch(); 
}
 ]]>
</mx:Script><mx:Form horizontalCenter=”0″ verticalCenter=”0″><mx:FormHeading label=”Please Login or you can click Public in the nav bar above if you don’t have an account.”/> 
<mx:FormItem label=”User Name:” height=”26″><mx:TextInput id=”txt_username”/></mx:FormItem><mx:FormItem label=”Password:” height=”26″><mx:TextInput id=”txt_password” displayAsPassword=”true”/></mx:FormItem>
<mx:FormItem><mx:Button label=”LOGIN” id=”btn_login” click=”verifyUser(event);”/></mx:FormItem></mx:Form>


A noter : Normalement tout ce qui concerne le style de l'application (les polices, les couleurs, etc. ) devrait être placé dans un fichier css mais je trouve personnellement qu'appliquer directement le style sur les composants est beaucoup plus adapté pour ce genre de petite démo rapide.

Dans le fichier PublicView.mxml, je vais rester très simple, ajouter juste un simple label.
Voici le texte de ce label : “Welcome ! This is the public page.”.

Dans le fichier ControlPanel.mxml, remplacer le code déjà présent par le suivant :

<?xml version=”1.0″ encoding=”utf-8″?><mx:Canvasxmlns:mx=”http://www.adobe.com/2006/mxml”
 width=”400″ height=”300″
 creationComplete=”init();”>
<mx:Script><![CDATA[
 
import com.model.ModelLocator;

[Bindable] public var __model:ModelLocator = ModelLocator.getInstance();
 
/** Initializes the control panel by enablingUI elements based on the users access privilegesthat are set in the model.
*  
   @param none
*  
   @return void

**/public function init():void 
{// The userRole determines which// buttons will be enabled or disabled

Admin.enabled = __model.admingranted;

Super.enabled = __model.supergranted; 
}
]]>
 </mx:Script>
<mx:VBox
    fontFamily=”Verdana” 
horizontalAlign=”center”
 verticalAlign=”middle”
 fontSize=”10″
 color=”#020202″ 
width=”100%”

    height=”100%”
 
    x=”0″ y=”0″>
 
    
<mx:Label text=”Welcome! This is the Secure page.” /><mx:Label text=”You are logged in with the User Role of”/><mx:Label text=”{__model.CURRENT_USER_ROLE}” 
fontFamily=”Verdana”
 fontSize=”12″ 
fontWeight=”bold”
 color=”#FC0202″/><mx:Button id=”Admin”
label=”Admin Functions”/><mx:Button id=”Super”
label=”Super Admin Functions”/><mx:Button id=”btn_common”
label=”Create Project”/><mx:Button id=”btn_common0″
label=”Assign Tasks”/>
</mx:VBox>
</mx:Canvas>


Nous devons maintenant notre fichier mxml principal, AccessControlExample.mxml ou le nom sous lequel vous avez sauvegardé ce fichier.
Remplacez tout le code contenu dans ce fichier avec celui-ci :

<?xml version=”1.0″ encoding=”utf-8″?><mx:Applicationxmlns:mx=”http://www.adobe.com/2006/mxml” 
xmlns:views=”com.views.*” 

xmlns:business=”com.business.*”
 
xmlns:control=”com.business.control.*”

layout=”absolute”
creationComplete=”init();”
viewSourceURL=”srcview/index.html”><mx:Script><![CDATA[
import mx.collections.ArrayCollection;

import mx.controls.Alert;

import com.model.ModelLocator;

 
[Bindable]
public var __model:ModelLocator = ModelLocator.getInstance();

 
/** Initilizes the main menu
* @param none
* @return void
**/public function init():void 
{var mnuItems:Array = [{label: "Public"}];
    
__model.MAINNAV = new ArrayCollection(mnuItems);

}
 

/** Handles navigation when a user
* clicks a button in the main nav bar
* 
   @param uint - Nav Bar Buttons SelectedIndex
* @return String - returns the lable text associated
* with the SelectedIndex
**/public function handleMainNav(i:uint):void{switch(__model.MAINNAV.getItemAt(i).label){caseControl Panel’:
               __model.workflowState = ModelLocator.CONTROLPANEL;

               break;
          
default:
              __model.workflowState = ModelLocator.PUBLIC;

              break;

    }
}
]]>
</mx:Script>
<!– Cairngorm Mappings –><business:Services id=”services” /><control:Controller id=”controller”/><mx:ViewStackid=”userViews” 
horizontalCenter=”0″ 
verticalCenter=”0″ 
selectedIndex=”{__model.workflowState}><views:Login/><views:ControlPanel/><views:PublicView /></mx:ViewStack>

 
     <mx:ApplicationControlBary=”100″ 
width=”400″ height=”36″
horizontalCenter=”0″><mx:MenuBar id=”mainNav” width=”100%” height=”100%” 
dataProvider=”{__model.MAINNAV}”
     
                                      click=”handleMainNav(mainNav.selectedIndex);” /></mx:ApplicationControlBar></mx:Application>

N'oubliez pas que pour la fonction verifyUser de notre fichier Login.mxml, nous devons donc créer une classe LoginEvent.as.

Clique droit sur /business/events, sélectionner New→Actionscript Class et nommer le fichier LoginEvent.as.

S'assurer que le package soit bien com.business.events

Définir la super-classe comme étant com.adobe.cairngorm.control.CairngormEvent

Cliquer sur Finish.

Remplacer le code du fichier créé par le suivant :

package com.business.events {
import com.adobe.cairngorm.control.CairngormEvent;
import com.vo.LoginVO;
import flash.events.Event;
public class LoginEvent extends CairngormEvent    {
public static const LOGIN:String = “Login”;
public var userVO:LoginVO;
public function LoginEvent(_userVO:LoginVO)  {
super(LOGIN);
this.userVO = _userVO;
}
override public function clone():Event  {
return new LoginEvent(userVO);
}
}
}


Créer un nouvelle classe Actionscript dans le dossier com/vo. Nommer ce fichier LoginVO.as.

Cliquer sur Finish.

Remplacer le contenu de ce fichier par ceci :

package com.vo {
import com.adobe.cairngorm.vo.IValueObject;
[Bindable]    
[RemoteClass(alias="LoginVO")]public class LoginVO implements IValueObject    
{public var username:String;
public var password:String;
 
     public function LoginVO(_username:String,_password:String)        
     {this.username = _username;
this.password = _password;
     }  
}
}

Sauvegarder et fermer ces deux fichiers, LoginEvent.as et LoginVO.as.

Créer un nouveau fichier Actionscript com/business/commands. Nommer le fichier LoginCommand.as.

Remplacer le code de ce fichier LoginCommand.as par celui-ci :

package com.business.commands 
{import com.adobe.cairngorm.commands.ICommand;

import com.adobe.cairngorm.control.CairngormEvent;

import com.business.delegates.LoginDelegate;

import com.business.events.LoginEvent;

import com.model.ModelLocator;

import mx.controls.Alert;
import mx.rpc.IResponder;
 
public class LoginCommand implements ICommand, IResponder 
{public var __model:ModelLocator = ModelLocator.getInstance();
 
    public function LoginCommand() {  }
 
    public function execute(event:CairngormEvent):void 
    {var loginEvent:LoginEvent = event as LoginEvent;

          var delegate:LoginDelegate = new LoginDelegate( this );

          delegate.verifyUser(loginEvent.userVO);

    }
 
    public function result(event:Object):void 
    {if(event.result)  
         {if(event.result == “FAILURE_CREDENTIAL_INVALID”) 
               {
                         
mx.controls.Alert.show(”Sorry! Either your User Name or your password are incorrect. Please try again or you can view the public page”);

                         return;
               
}var accessPrivs:Object = event.result;
                          
if(accessPrivs["viewRestrictedUI"]==”allowed”) 
                          {// set default privileges

                                __model.supergranted = false;
                                
__model.admingranted = false;

                                __model.MAINNAV.addItem({label: “Control Panel”});
                                
__model.workflowState = ModelLocator.CONTROLPANEL;

                                __model.CURRENT_USER_ROLE = accessPrivs["userRole"];
 
                               
if(__model.CURRENT_USER_ROLE == “Super) 
                               {

                                       __model.supergranted = true;
__model.admingranted = true;
                               
} 
                               else if(__model.CURRENT_USER_ROLE == “admin”) 
                               {
             
                                   __model.supergranted = false;
__model.admingranted = true;
                               }
                        } else {

                               __model.workflowState = ModelLocator.PUBLIC;
                        
}
 
                        
// DEBUG MODE

                       trace([User Login] - user Role:” +accessPrivs["userRole"]);

                       trace([User Login] - viewPublicUI:” +accessPrivs["viewPublicUI"]);
                       
trace([User Login] - viewRestrictedUI:” +accessPrivs["viewRestrictedUI"]); 
                       
trace([User Login] - createManager:” +accessPrivs["createManager"]);
                       
trace([User Login] - viewLogs:” +accessPrivs["viewLogs"]);

                }}
   public function fault(event:Object):void  
   {trace([User Login] - Error Connecting!” + event.toString());
   
}
}}

Créer une nouvelle classe Actionscript dans com/business/delegates. Nommer ce fichier LoginDelegate.as.
Remplacer le code de fichier LoginDelegate.as par le suivant :

package com.business.delegates 
{/**
  * LoginDelegate
  *  Passes the users credentials as a value object tothe service.
  **/
import mx.rpc.IResponder;

import com.vo.LoginVO;

import com.adobe.cairngorm.business.ServiceLocator;
 
public class LoginDelegate 
{
   private var responder : IResponder;
private var service : Object;
/**
* Initilizes the service call
  * @param IResponder responder
  * @return nothing
  **/
 
     public function LoginDelegate( responder:IResponder ) 
     {this.responder = responder;
this.service = ServiceLocator.getInstance().getRemoteObject(”LoginService”);
     
}
     /**
* Command that actually calls the service and addsthe responder mapping to the service method call
* 
           @param LoginVO login
* 
          @return void

      **/public function verifyUser(login:LoginVO):void 
     {var call:Object = service.verifyUser( login );

        call.addResponder( responder ); 
     
}
  }
}


Nous devons maintenant créer un contrôleur pour mappé notre LoginEvent à LoginCommand.
Dans le dossier com/business créer une nouvelle classe Actionscript nommé Controller.as.
Remplacer le contenu de ce fichier par le code suivant :

package com.business.control 
{import com.adobe.cairngorm.control.FrontController;

import com.business.commands.*;

import com.business.events.*;
 
/**
* Controller
* Mappings of Cairngorm Events to Cairngorm commands
**/public class Controller extends FrontController
{/**
* Class constructor
* 
    @param none

**/public function Controller() 
{
    this.initialize();

}
/**
* Initializes the mappings
* 
  @param none
*
  @return void

**/public function initialize():void
  {//ADD COMMANDSthis.addCommand(LoginEvent.LOGIN,LoginCommand);

  }}}

Passons maintenant au code PHP.

En savoir plus