Wir wollen unsere Bibliotheksanwendung um ein Login erweitern. Dafür benötigen wir für das Eingabeformular ein neues Modul. Die eigentliche Authentifikation des Nutzers soll über ein Plugin (angelehnt an folgendes Tutorial) realisiert werden. Plugins haben bei Zend den Vorteil, dass sie im Anwendungszyklus nach der Initialisierung stets verfügbar sind. Auf den Loginstatus kann also später einmal innerhalb der Anwendung von überall aus zugegriffen werden.
Die Logindaten der Nutzer sollen in unserer SQLite-Datenbank „bibliothek.sqlite“ gespeichert werden. Über den SQLite Manager des Firefox legen wir eine neue Tabelle „users“ in unserer Datenbank an.
CREATE TABLE "users" ("u_id" INTEGER PRIMARY KEY NOT NULL ,"u_username" VARCHAR NOT NULL ,"u_passwort" VARCHAR NOT NULL ,"u_role" VARCHAR NOT NULL )
Danach fügen wir der Tabelle users zwei Nutzer hinzu: Einen Administrator und einen Nutzer test.
INSERT INTO "users" VALUES ("1","admin","admin","admin"); INSERT INTO "users" VALUES ("2","test","test","user");
Danach wechseln wir ins Eclipse in unseren Projektordner. Wir wollen uns zunächst um die Logik hinter unserem Login (also das Plugin zur Authentifikation des Nutzers) kümmern.
Im Ordner application legen wir einen Ordner „Plugin“ an. In diesem Ordner legen wir einen Unterordner „Auth“ an. Es ergibt sich also die Verzeichnisstruktur application/Plugin/Auth. Achtet auch auf die casesensitive Schreibweise in der Ordnerstruktur.
In dem Ordner application/Plugin/Auth/ legen wir die folgenden drei Dateien an:
- AccessControl.php
- Acl.php
- AuthAdapter
Diese drei Klassen übernehmen bei unserem Loginvorgang verschiedene Aufgaben. Die Klasse AuthAdapter.php regelt den Zugang zur Datenbank. Es wird hier der Tabellenname, sowie die Spalte für den Usernamen und die Spalte für das Passwort gesetzt. Die Datei Acl.php definiert unsere Benuterrollen und deren Rechte. Die Datei AccessControl.php ist für das Login-Handling zuständig. Sie führt die Datenbankabfrage bzgl. der Nutzerdaten durch und prüft, ob das Login korrekt ist.
Beginnen wir mit der AuthAdapter.php. In diese Datei kopiert ihr folgenden Inhalt:
class Plugin_Auth_AuthAdapter extends Zend_Auth_Adapter_DbTable { public function __construct() { parent::__construct(); $this->setTableName('users'); $this->setIdentityColumn('u_username'); $this->setCredentialColumn('u_passwort'); } }
Weiter geht es mit der Datei Acl.php. Diese bekommt folgenden Inhalt:
class Plugin_Auth_Acl extends Zend_Acl { public function __construct() { // RESSOURCES $this->addResource(new Zend_Acl_Resource('bibo')); $this->addResource(new Zend_Acl_Resource('login')); $this->addRole(new Zend_Acl_Role('guest')); $this->addRole(new Zend_Acl_Role('user'), 'guest'); $this->addRole(new Zend_Acl_Role('admin'), 'user'); //Allen werden alle Rechte entzogen $this->deny(null, null); //Roles, Ressources,Action //Admin alle Rechte in Login und Bibo geben $this->allow('admin', 'bibo', null); $this->allow('admin', 'login', null); //Benutzerrechte für den Gast setzen $this->allow('guest', 'bibo', 'index'); $this->allow('guest', 'login', 'index'); //Benutzerrechte für den eingeloggten User setzen $this->allow('user', 'login', 'logout'); $this->allow('user', 'bibo', 'ausleihen'); $this->allow('user', 'bibo', 'leihen'); $this->allow('user', 'bibo', 'zurueck'); } }
Wir definieren als Resourcen unsere zwei Module bibo und login (Login wird später als Modul noch angelegt). Danach definieren wir unsere Benutzerrollen. Wir legen eine Gast-Rolle an. Danach folgt die Rolle des Users, welcher von Gast erbt. Daraufhin erstellen wir die Rolle des Administrators, welcher wiederrum von User erbt.
Danach werden allen Rollen alle Rechte auf allen Resourcen entzogen und danach schrittweise in den entsprechenden Rollen wieder gesetzt.
Um unser Plugin zu komplettieren bearbeiten wir jetzt die Datei AccessControl.php und ersetzen sie durch den folgenden Inhalt:
class Plugin_Auth_AccessControl extends Zend_Controller_Plugin_Abstract { protected $_auth; protected $_acl; public function __construct(Zend_Auth $auth, Zend_Acl $acl) { $this->_auth = $auth; $this->_acl = $acl; } public function routeStartup(Zend_Controller_Request_Abstract $request) { if (!$this->_auth->hasIdentity() && null !== $request->getPost('login_user') && null !== $request->getPost('login_password')) { // POST-Daten bereinigen $filter = new Zend_Filter_StripTags(); $username = $filter->filter($request->getPost('login_user')); $password = $filter->filter($request->getPost('login_password')); if(!empty($username)) { //Datenbankabfrage $authAdapter = new Plugin_Auth_AuthAdapter(); $authAdapter->setIdentity($username); $authAdapter->setCredential($password); $result = $this->_auth->authenticate($authAdapter); //Fehlermeldung setzen, wenn Login falsch if($result->getCode() != 1) { $view = Zend_Layout::getMvcInstance()->getView(); $view->assign(array('fehler'=>'Login inkorrekt! Bitte korrekten Login eingeben!')); } if($result->getCode() == 1) { $view = Zend_Layout::getMvcInstance()->getView(); $view->assign(array('weiterleitung'=>'true')); } if ($result->isValid()) { $storage = $this->_auth->getStorage(); // die gesamte Tabellenzeile in der Session speichern, // wobei das Passwort unterdrückt wird $storage->write($authAdapter->getResultRowObject(null, 'password')); } } } } public function preDispatch(Zend_Controller_Request_Abstract $request) { if ($this->_auth->hasIdentity() && is_object($this->_auth->getIdentity())) { $role = $this->_auth->getIdentity()->u_role; } else { $role = 'guest'; } $module = $request->getModuleName(); // Ressourcen = Modul -> kann hier geändert werden! $resource = $module; if (!$this->_acl->has($resource)) { $resource = null; } if (!$this->_acl->isAllowed($role, $resource, $request->getActionName())) { if ($this->_auth->hasIdentity()) { // angemeldet, aber keine Rechte -> Fehler! $request->setModuleName('login'); $request->setControllerName('error'); $request->setActionName('noaccess'); } else { // nicht angemeldet -> Login $request->setModuleName('login'); $request->setControllerName('index'); $request->setActionName('index'); } } } }
Die gerade bearbeitete Datei ist für das Login-Handling zuständig. Sie führt die Datenbankabfrage bzgl. der Nutzerdaten durch und prüft, ob das Login korrekt ist. Für den Fall, dass der Login nicht korrekt ist, oder der Nutzer keine Rechte besitzt wird die Weiterleitung auf eine Fehlerseite oder das Loginpanel vorgenommen. Damit ist der Quellcode unseres Plugins fertig.
Nun kümmern wir uns um das Login-Modul. Wechselt im Putty in das Verzeichnis eurer Anwendung (ggf. euren Pfad anpassen).
cd bibotest/htdocs/bibotest/
Führt danach folgenden Befehl zum Anlegen eines neuen Moduls „login“ aus:
zf create module login
Klickt im Eclipse auf den Ordner application/modules und drückt danach auf die F5-Taste eurer Tastatur. Ihr solltet nun einen weiteren Ordner login darin finden, der die Unterordner controllers, models und views beinhaltet. Fügt im Ordner application/modules/login einen weiteren Unterordner „forms“ für unser Loginformular hinzu. Dazu legen wir in dem Ordner forms eine Datei LoginForm.php mit folgendem Inhalt an:
class Login_Form_LoginForm extends Zend_Form { public function __construct($options = null) { parent::__construct($options); $this->setAction('') ->setMethod('post'); $user = new Zend_Form_Element_Text('login_user'); $user->setLabel('Login-Name') ->setRequired(true) ->addValidator('NotEmpty') ->addErrorMessage('Bitte einen Nutzernamen eintragen'); $password = new Zend_Form_Element_Password('login_password'); $password->setLabel('Passwort') ->setRequired(true) ->addValidator('NotEmpty') ->addErrorMessage('Bitte ein Passwort eintragen'); $submit = new Zend_Form_Element_Submit('submit'); $submit->setLabel('Anmelden'); $this->addElements(array($user, $password, $submit)); } }
Es wird ein einfaches Loginformular mit Textfeldern für Login und Passwort, sowie einen Submit-Button angelegt.
Damit unser Login-Formular aufgerufen werden kann benötigen wir für das Modul „login“ einen IndexController. Wir erzeugen zu diesem Zweck eine Datei IndexController.php im Ordner application/modules/login/controllers/.
class Login_IndexController extends Zend_Controller_Action { protected $_redirector = null; //Loginformular erzeugen public function indexAction() { //Formularerzeugung über ZendForm $form = new Login_Form_LoginForm(); $this->view->form = $form; //Prüfen, ob Formular abgeschickt if($this->_request->isPost()) { $formData = $this->_request->getPost(); //Prüfen on if($form->isValid($formData)) { //Logindaten/Fehlerdaten (erneut) eintragen, bei fehlerhaftem Login $form->populate($formData); //Weiterleitung, wenn einloggen erfolgreich if(isset($this->view->weiterleitung)) { $this->_redirect("/"); } } else { $this->view->logout = false; } } } //Ausloggen public function logoutAction() { Zend_Auth::getInstance()->clearIdentity(); $this->view->logout = true; $this->_redirect("/"); } }
Passend dazu legen wir uns im Ordner application/modules/login/views/scripts/index/ eine index.phtml an (Unterordner ggf. mit anlegen) und schreiben folgenden Inhalt hinein:
<div id="login"> <?php echo $this->form; if(isset($this->fehler)) { echo '<div class="errors">'; echo $this->fehler; echo '</div>'; } ?> </div>
Es wird hier eigentlich nur das Formular ausgegeben. Im Fehlerfall soll zusätzlich eine Fehlermeldung erscheinen. Für diesen Zweck benötigen wir noch einen Error Controller. Im Controller-Ordner des Moduls login application/modules/login/controllers/ legen wir die Datei ErrorController.php an:
class Login_ErrorController extends Zend_Controller_Action { public function errorAction() { $errors = $this->_getParam('error_handler'); if (!$errors) { $this->view->message = 'You have reached the error page'; return; } switch ($errors->type) { case Zend_Controller_Plugin_ErrorHandler::EXCEPTION_NO_ROUTE: case Zend_Controller_Plugin_ErrorHandler::EXCEPTION_NO_CONTROLLER: case Zend_Controller_Plugin_ErrorHandler::EXCEPTION_NO_ACTION: // 404 error -- controller or action not found $this->getResponse()->setHttpResponseCode(404); $this->view->message = 'Page not found'; break; default: // application error $this->getResponse()->setHttpResponseCode(500); $this->view->message = 'Application error'; break; } // Log exception, if logger available if ($log = $this->getLog()) { $log->crit($this->view->message, $errors->exception); } // conditionally display exceptions if ($this->getInvokeArg('displayExceptions') == true) { $this->view->exception = $errors->exception; } $this->view->request = $errors->request; } public function noaccessAction() { } public function getLog() { $bootstrap = $this->getInvokeArg('bootstrap'); if (!$bootstrap->hasResource('Log')) { return false; } $log = $bootstrap->getResource('Log'); return $log; } }
Passend zum Controller benötigen wir noch die entsprechenden View-Scripte. Im Ordner application/modules/login/views/scripts/error/ (Unterordner ggf. anlegen) folgende Dateien anlegen:
- error.phtml
- noaccess.phtml
<div id="exception"> <h1> Error </h1> <h2><?php echo $this->exception->getMessage() ?></h2> <h3>Exception information:</h3> <h3>Stack trace:</h3> <pre><?php echo $this->exception->getTraceAsString() ?></pre> <h3>Request Parameter:</h3> <pre><?php echo var_export($this->request->getParams(), true) ?></pre> </div>
In die Datei noaccess.phtml schreiben wir lediglich:
Keine Zugriffsrechte
Damit ist das Modul fast fertig. Es fehlt noch die zum Modul gehörige Bootstrap.php. Im Modulordner application/modules/login/ legen wir eine Bootstrap.php mit folgendem Inhalt an:
class Login_Bootstrap extends Zend_Application_Module_Bootstrap { }
Plugin „login“ ist fertig, Modul „bibo“ ist fertig. Nun muss beides noch entsprechend initialisiert werden.
In der Datei application/configs/application.ini fügt ihr unter die Zeile
autoloaderNamespace.bibo = "Bibo_"
die folgenden beiden Zeilen ein:
autoloaderNamespace.login = "Login_" autoloadernamespaces.0 = "Plugin_"
Wir öffnen die Bootstrap-Datei der Webanwendung application/Bootstrap.php und ersetzen deren Inhalt durch den folgenden:
class Bootstrap extends Zend_Application_Bootstrap_Bootstrap { /** * * Initialisierung der Benutzerauthentifikation */ protected function _initAuth() { $this->bootstrap('frontController'); $auth = Zend_Auth::getInstance(); $acl = new Plugin_Auth_Acl(); $this->getResource('frontController') ->registerPlugin(new Plugin_Auth_AccessControl($auth, $acl)) ->setParam('auth', $auth); } }
Wechselt in euren Browser auf eure Projekt-Url (vorher Browsercache leeren). Die Buchübersichtsseite sollte weiterhin für jeden erreichbar sein. Für die Funktion „ausleihen“ müsst ihr euch bereits einloggen. Hier sollten beide Logins (admin, admin bzw. test,test) funktionieren. Für die Aktion des Bearbeitens ist lediglich der Login des Administrators freigeschaltet. Zum Ausloggen müsst ihr aktuell noch euren Browsercache leeren. Einfacher wird dies, wenn ihr eure Layoutdatei des Projekts ein wenig modifiziert. Öffnet eure Datei application/layouts/scripts/bibo.phtml und ersetzt deren Inhalt durch den untenstehenden Code:
<html xmlns="http://www.w3.org/1999/xhtml"> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <title>Bibliothek</title> </head> <body> <div> <?php $status = Zend_Auth::getInstance()->getStorage()->read(); if($status) { echo $status->u_username; echo ' <a href="/login/index/logout/">Logout</a>'; } else { echo ' <a href="/login/index/index/">Login</a>'; } ?> </div> <div><?php echo $this->layout()->content; ?></div> </body> </html>
Dadurch erhaltet ihr im Kopf eurer Anwendung ein div-Container mit der Login- bzw. Logoutfunktionalität. Wenn ihr eingeloggt seit, steht zudem eurer Loginname neben dem Logout-Link.
Nach der Ergänzung der application/Bootstrap.php ergibt sich bei mir ein Fehler, den ich nicht behoben kriege.
Der Aufruf von registerPlugin(…) scheitert bei mir.
Leicht modifiziert, um die problematische Stelle eindeutig zu identifizieren:
protected function _initAuth() {
$this->bootstrap(‚frontController‘);
$auth = Zend_Auth::getInstance();
$acl = new Plugin_Auth_Acl();
$this->getResource(‚frontController‘);
$plug= new Plugin_Auth_AccessControl($auth, $acl);
$this->registerPlugin($plug);
$this->setParam(‚auth‘, $auth);
} // function _initAuth()
führt bei mir zu folgendem Stacktrace:
Fatal error: Uncaught exception ‚Zend_Application_Bootstrap_Exception‘ with message ‚Invalid method „registerPlugin“‚ in /usr/share/php/libzend-framework-php/Zend/Application/Bootstrap/BootstrapAbstract.php:601
Stack trace:
#0 [internal function]: Zend_Application_Bootstrap_BootstrapAbstract->__call(‚registerPlugin‘, Array)
#1 /daten/www/htdocs/bibotest/application/Bootstrap.php(15): Bootstrap->registerPlugin(Object(Plugin_Auth_AccessControl))
#2 /usr/share/php/libzend-framework-php/Zend/Application/Bootstrap/BootstrapAbstract.php(665): Bootstrap->_initAuth()
#3 /usr/share/php/libzend-framework-php/Zend/Application/Bootstrap/BootstrapAbstract.php(618): Zend_Application_Bootstrap_BootstrapAbstract->_executeResource(‚auth‘)
#4 /usr/share/php/libzend-framework-php/Zend/Application/Bootstrap/BootstrapAbstract.php(582): Zend_Application_Bootstrap_BootstrapAbstract->_bootstrap(NULL)
#5 /usr/share/php/libzend-framework-php/Zend/Application.php(355): Zend_Application_Bootstrap_BootstrapAbstract->bootstrap(NULL)
#6 /daten/www/htdo in /usr/share/php/libzend-framework-php/Zend/Application/Bootstrap/BootstrapAbstract.php on line 601
Hast Du eine Idee, was da schief läuft?
Danke und viele Grüße,
Norbert
Überprüfe mal, ob deine application.ini folgende Einträge enthält:
Ansonsten siehts erstmal ganz gut aus. Zend ist auch korrekt eingebunden? Wenn ja in welcher Version?
[…] aus Datenbank Hallo Profis, nach dem ich mit dem Tutoriell ein Login gebaut habe, habe ich zu ACL folgende Frage. In der Bootstrap wird mit der Zeile $acl = […]
[…] "Wohnort", "Bezahlsystem" usw. Das Auth-Plugin kommt aus diesem Tutorial: Zend Tutorial – 04 – Login | Nikos Web. Im Prinzip möchte ich 4 unterschiedliche Loginseiten haben, darüber müsste doch irgendwie eine […]
Hallo,
zunächst einmal großes Lob für die Beispiele. Jedoch bekomme ich das Modul nicht ans laufen. Ist es möglich, mir den Quellcode per Email zu senden?
Vielen Dank im Voraus.
Gruß
Markus
@Norbert:
protected function _initAuth() {
$this->bootstrap(‘frontController’);
$auth = Zend_Auth::getInstance();
$acl = new Plugin_Auth_Acl();
$plug= new Plugin_Auth_AccessControl($auth, $acl);
$this->getResource(‘frontController’)
->registerPlugin($plug);
->setParam(‘auth’, $auth);
} // function _initAuth()