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.
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 :
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 :
Configurer l'Acl peut vite devenir assez compliqué mais je vais faire en sorte que ça reste simple pour l'instant.
Voilà à quoi devrais ressembler la structure de votre projet :
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’);
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.
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-definition id=”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.
Dans le dossier src, créer la structure suivante :
(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:ServiceLocator xmlns:mx=”http://www.adobe.com/2006/mxml” xmlns:rds=”com.adobe.cairngorm.business.*”> <mx:RemoteObject id=”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 HERE public 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:Canvas xmlns: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:Application xmlns: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) { case ‘Control 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:ApplicationControlBar y=”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 COMMANDS this.addCommand(LoginEvent.LOGIN,LoginCommand); } } }
Passons maintenant au code PHP.
Encore des questions? Besoin d'aide? Venez en discuter sur les forums Flash Remoting ou Programmation Dynamique.