Wärend die Beispiele für die Basisnutzung völlig akzeptierbar sind, in ihrem Weg Zend Framework Sessions
zu Benutzen, ist auch etwas beste Praxis zu bedenken. Bedenkt man etwa das
Zend_Auth
Beispiel
das Zend_Session_Namespace standardmäßig transparent benutzt um Authentifizierungs Teile zu fixieren.
Dieses Beispiel zeigt einen Ansatz um schnell und einfach Zend_Session_Namespace und Zend_Auth zu integrieren.
Wenn alle Anfragen eine Session besitzen und Zend Framework Sessions benutzen sollen, muß die Session in der Bootstrap Datei gestartet werden:
Beispiel 30.6. Starten einer globalen Session
<?php ... require_once 'Zend/Session.php'; Zend_Session::start(); ... ?>
Durch das Starten der Session in der Bootstrap Datei verhindert man das die Session gestartet werden
könnte nachdem die Header an den Browser gesendet wurde, was zu einer Ausnahme und möglicherweise
zu einer fehlerhaften Seiten im Browser führen würde. Viele gehobenen Features benötigen zuerst
Zend_Session::start()
. (Mehr dazu später in den gehobenen Features)
Es gibt vier Wege eine Session zustarten wenn Zend_Session verwendet wird. Zwei sind falsch.
1. Falsch: PHP's session.auto.start Ini-Einstellung darf nicht gesetzt sein, weder in der
php.ini noch in .htaccess (http://www.php.net/manual/en/ref.session.php#ini.session.auto-start).
Wenn keine Möglichkeit besteht diese Einstellung in php.ini zu deaktivieren, und mod_php
(oder ähnliches) verwendet wird, und die Einstellung schon in php.ini aktiviert ist, kann
php_value session.auto_start 0
in der .htaccess Datei hinzugefügt werden
(normalerweise im HTML Dokument Haupt-Verzeichnis).
2. Falsch: PHP's session_start()
Funktion darf nicht direkt verwendet werden. Wenn session_start()
direkt, und
anschließend Zend_Session_Namespace verwendet wird, wird von Zend_Session::start()
eine Ausnahme geworfen ("session has already been started"). Wenn
Zend_Session::start()
ausgerufen wird, nachdem Zend_Session_Namespace verwendet
wird oder Zend_Session::start()
explizit verwendet wird, wird ein Fehler vom
Level E_NOTICE erzeugt und der Aufruf wird ignoriert.
3. Richtig: Verwenden von Zend_Session::start()
. Wenn es gewünscht ist, das alle
Anfragen eine Session haben und verwenden, sollte diese Funktion sehr früh, direkt und
entscheidungslos in der ZF Bootstrap Datei aufgerufen werden. Session haben einigen Overhead.
Wenn einige Anfragen Sessions benötigen aber andere Anfragen keine Sessions verwenden, dann:
Entscheidungslos, die strict
Option auf wahr setzen (siehe
Zend_Session::setOptions()
) im eigenen Bootstrap.
Aufruf von Zend_Session::start()
, nur für die Anfragen die eine Session
verwenden müssen, bevor das erste Mal new Zend_Session_Namespace()
aufgerufen
wird.
Normales verwenden von new Zend_Session_Namespace()
wo es benötigt wird,
aber sicherstellen das davor Zend_Session::start()
ausgerufen wurde.
Die Option strict
verhindert das new Zend_Session_Namespace()
automatisch eine Session startet und dabei Zend_Session::start()
verwendet.
Deshalb hilft diese Option Entwicklern bei eigenen ZF Anwendungen, sich für ein Design entscheiden
zu können welches verhindert das für bestimmte Anfragen Sessions verwendet werden, da ein
Fehler geworfen würde wenn diese Option verwendet und Zend_Session_Namespace instanziiert wird,
bevor Zend_Session::start()
explizit aufgerufen wird. Diese Option sollte nicht in
ZF Kern-Bibliotheks-Code verwendet werden, da nur Entwickler diese Designentscheidung treffen
sollten. Gleichfalls sollten alle "Bibliotheks" Entwickler vorsichtig entscheiden welchen Einfluß
die Verwendung von Zend_Session::setOptions()
auf Benutzern in deren
Bibliothekscode hat, da diese Optionen globale Seiteneffekte hat (genauso wie die
darunterliegende Option für ext/session).
4. Richtig: Einfach new Zend_Session_Namespace()
verwenden wo dies auch immer
notwendig ist, und die Session wird automatisch innerhalb von Zend_Session gestartet. Das bietet
eine extrem simple Handhabung die in den meisten Situationen gut funktioniert. Trotzdem ist man
dann dafür verantwortlich darauf zu schauen das das erste new Zend_Session_Namespace()
passiert bevor irgendeine Ausgabe (z.B.
HTTP headers ) von PHP an den Client
gesendet wird, wenn standardmäßige, Cookie-basierte Sessions verwendet werden (sehr
empfehlenswert). Siehe Abschnitt 30.4.3.1, „Header schon gesendet“ für mehr Informationen.
Session Namensräume können gesperrt werden um weitere Veränderungen der Daten in diesem Namensraum
zu verhindern. Die Verwendung von Zend_Session_Namespace's lock()
macht einen speziellen
Namensraum nur-lesbar, unLock()
macht einen nur-lesbaren Namensraum les- und schreibbar,
und isLocked()
prüft ob ein Namensraum vorher gesperrt wurde. Sperren sind flüchtig und
bestehen nicht von einer Anfrage zur nächsten. Die Sperre des Namensraumes hat keinen Effekt auf
Setz-Methoden von Objekten welche im Namensraum gespeichert sind, aber sie verhindert die Verwendung
der Setz-Methoden des Namensraumes welche das gespeicherte Objekt direkt im Namensraum löschen oder
ersetzen. Gleichwohl verhindert das Sperren von Zend_Session_Namespace Namensräumen nicht die Verwendung
von symbolischen Tabellen-Aliasen auf die gleichen Daten (siehe
PHP references).
Beispiel 30.7. Sperren von Session Namensräumen
<?php // Annahme: $userProfileNamespace = new Zend_Session_Namespace('userProfileNamespace'); // Die Session als nur-lesbar gesperrt markieren $userProfileNamespace->lock(); // Die nur-lesbar Sperre aufheben if ($userProfileNamespace->isLocked()) { $userProfileNamespace->unLock(); } ?>
Es gibt eine Vielzahl von Ideen wie Modelle im MVC Paradigma für das Web gehandhabt werden, welche die Erstellung von Präsentations Modellen für Views inkludieren. Manchmal sind bestehende Daten, egal ob Teil des Domain Modells oder nicht, adequat für diese Aufgabe. Um Views vom Hinzufügen von irgendeiner ausführenden Logik abzuraten, die solche Daten verändert, sollte der Session Namensraum gesperrt werden bevor es Views gestattet wird auf dieses Subset des "Präsentations" Modells zuzugreifen.
Beispiel 30.8. Sperren von Sessions in Views
<?php class FooModule_View extends Zend_View { public function show($name) { if (!isset($this->mySessionNamespace)) { $this->mySessionNamespace = Zend::registry('FooModule'); } if ($this->mySessionNamespace->isLocked()) { return parent::render($name); } $this->mySessionNamespace->lock(); $return = parent::render($name); $this->mySessionNamespace->unLock(); return $return; } } ?>
Limits können plaziert werden an der Lebensdauer von beidem, Namensräumen und individuellen Schlüsseln
in Namensräumen. Normale Anwendungsfälle beinhalten das durchlaufen von temporären Informationen zwischen
Anfragen, und das vermindern der Aufdeckung von vielfältigen Sicherheitsrisiken durch das Entfernen des
Zugangs zu potentiell sensitiven Informationen, manchmal nachdem Authentifizierung stettgefunden hat.
Das Ende kann auf abgelaufenen Sekunden, oder auf dem Konzept von "Sprüngen" basieren, wobei ein Sprung
für jede nachfolgende Anfrage stattfindet die den Namensraum aktiviert, durch mindestens ein
$space = new Zend_Session_Namespace('myspace');
.
Beispiel 30.9. Beispiel für den Verfall
<?php $s = new Zend_Session_Namespace('expireAll'); $s->a = 'Apfel'; $s->p = 'Pfirsich'; $s->o = 'Orange'; $s->setExpirationSeconds(5, 'a'); // Der Schlüssel "a" läuft in 5 Sekunden ab // Der komplette Namensraum läuft in 5 "Sprüngen" $s->setExpirationHops(5); $s->setExpirationSeconds(60); // Der "expireAll" Namensraum wird als "abgelaufen" markiert // sobald der erste Aufruf empfangen wurde und 60 Sekunden // vergangen sind, oder in 5 Sprüngen, was auch immer zuerst stattfindet ?>
Wenn mit Daten einer Session gearbeitet wird, die in der aktuellen Anfrage ablaufen, sollte Vorsicht beim Empfangen dieser Daten gehalten werden. Auch wenn diese Daten durch Referenz zurückgegeben werden, wird die Änderung derselben, diese Daten nicht über diese Abfrage hinweg gültig machen. Um die Zeit für das Ablaufen zu "resetieren", müssen die Daten in eine temporäre Variable geholt werden, diese im Namensraum entfernt und anschliessend der entsprechende Schlüssel wieder gesetzt werden.
Namensräume können auch verwendet werden um den Zugriff auf Sessions durch Kontroller zu seperieren um Variablen vor Kontaminierung zu schützen. Zum Beispiel könnte der 'Zend_Auth' Kontroller seine Session Daten von allen anderen Kontrollern seperat halten.
Beispiel 30.10. Session Namensräume für Kontroller mit automatischem Verfall
<?php require_once 'Zend/Session.php'; // Abfrage des View Kontrollers $testSpace = new Zend_Session_Namespace('testSpace'); $testSpace->setExpirationSeconds(300, "accept_answer"); // Nur diese Variable ablaufen lassen $testSpace->accept_answer = true; -- // Anwort des Ausführenden Kontrollers $testSpace = new Zend_Session_Namespace('testSpace'); if ($testSpace->accept_answer === true) { // Innerhalb der Zeit } else { // Ausserhalb der Zend } ?>
Die Verwendung der Session Absperrung wird empfohlen (siehe oberhalb) statt dem untigen Feature, welches dem Entwickler extra Management aufbürdet um Zend_Session_Namespace Instanzen zu beliebigen Funktionen und Objekten zu übergeben welche auf jeden Namensraum zugreifen müssen.
Wenn die erste Instanz von Zend_Session_Namespace für einen speziellen Namensraum erstellt wird, kann
Zend_Session_Namespace dazu instruiert werden keine weiteren Instanzen für diesen Namensraum zu machen.
Jeder weitere Versuch eine Zend_Session_Namespace Instanz zu erstellen die den gleichen hat wird einen
Fehler werfen. Dieses Verhalten ist optional, und nicht das Standardverhalten, bleibt aber verfügbar,
für jene, die das Herumreichen eines einzigen Instanz Objektes für jeden Namensraum, bevorzugen.
Das erhöht den Schutz vor Änderungen durch Komponenten welche einen speziellen Session Namensraum nicht
ändern sollten, weil Sie keinen einfachen Zugriff haben. Trotzdem kann das limitieren eines
Namensraumes an eine einzelne Instanz zu mehr und komplexerem Code führen, da der Zugriff auf
das gewöhnliche $aNamespace = new Zend_Session_Namespace('aNamespace');
entfernt wird,
nachdem die Instanz erstellt wurde, wie im beiliegenden Beispiel gezeigt:
Beispiel 30.11. Limitierung auf einzelne Instanzen
<?php require_once 'Zend/Session.php'; $authSpaceAccessor1 = new Zend_Session_Namespace('Zend_Auth'); $authSpaceAccessor2 = new Zend_Session_Namespace('Zend_Auth', Zend_Session_Namespace::SINGLE_INSTANCE); $authSpaceAccessor1->foo = 'bar'; assert($authSpaceAccessor2->foo, 'bar'); // gestattet doSomething($options, $authSpaceAccessor2); // gib den Zugriff auf wo immer er benötigt wird . . . $aNamespaceObject = new Zend_Session_Namespace('Zend_Auth'); // Das wird einen Fehler werfen ?>
Der zweite Parameter oben im Konstruktor sagt Zend_Session_Namespace das jede zukünftige
Zend_Session die mit dem 'Zend_Auth' Namensraum initiiert wird, nicht erlaubt ist, und eine
Ausnahme hervorruft. Da new Zend_Session_Namespace('Zend_Auth')
nicht erlaubt ist nachdem
der obige Code ausgeführt wurde, wird der Entwickler darauf aufmerksam gemacht die Instanz des Objektes
($authSpaceAccessor2
im obigen Beispiel) woanders zu speichern, wenn ein Zugriff auf diesen
Session Namensraum später, wärend der gleichen Anfrage, benötigt wird. Ein Entwickler könnte, zum
Beispiel, die Instanz in einer statischen Variable speichern, oder an eine andere Methode weiterreichen
welche Zugriff auf diesen Session Namensraum benötigt. Die Sperre der Session (siehe oben) bietet einen
gewöhnlicheren Weg, und ist weniger umständlicher im limitieren der Zugriffe auf Namensräume.
Ändern eines Array innerhalb eines Namensraumes funktioniert nicht. Die einfachste Lösung ist es das Array zu speichern nachdem alle gewünschten Werte geändert wurden. ZF-800 dokumentiert ein bekanntes Problem welches viele PHP Anwendungen betrifft die magische Methoden mit Arrays verwenden.
Beispiel 30.12. Bekannte Probleme mit Arrays
<?php $sessionNamespace = new Zend_Session_Namespace('Foo'); $sessionNamespace->array = array(); $sessionNamespace->array['testKey'] = 1; // funktioniert nicht vor PHP 5.2.1 ?>
Wenn ein Array geändert werden soll nachdem es einem Session Namensraum Schlüssel zugewiesen wurde, muß das Array geholt, anschließend geändert und das Array in den Session Namensraum zurück gespeichert.
Beispiel 30.13. Workaround: Holen, Ändern, Speichern
<?php $sessionNamespace = new Zend_Session_Namespace('Foo'); $sessionNamespace->array = array('baum' => 'apfel'); $tmp = $sessionNamespace->array; $tmp['frucht'] = 'pfirsich'; $sessionNamespace->array = $tmp; ?>
Alternativ kann ein Array, welches Referenzen enthält, in ein gewünschtes Array gespeichert und anschließend indirekt darauf zugegriffen werden.
Wenn der Authentifizierung Adapter für Zend_Auth
ein Ergebnis zurückgibt wo die Identität der
Authentifizierung ein Objekt (nicht benötigt) statt einem Array ist, sollte sichergestellt werden das
die Definition der Klasse die die Identität der Authentifizierung enthält vor dem Starten der Session
geladen wird. Stattdessen wird empfohlen die berechneten Authentifizierung Id's im Authentifizierungs
Adapter innerhalb eines gut bekannten Schlüssels im Session Namensraum zu Speichern. Das Standardverhalten
von Zend_Auth
, zum Beispiel, plaziert ihn im 'storage' Schlüssel des 'Zend_Auth' Namensraumes.
Wenn Zend_Auth
mitgeteilt wird, nicht im Authentifizierungs Token der Session zu bleiben,
kann die Authentifizierung Id manuell im Session Namensraum gespeichert werden, innerhalb einer
gut bekannten Position in einem Session Namensraum der eigenen Wahl. Oft haben Anwendungen spezielle
Notwendigkeiten darüber wo benötige Zeugnisse (wenn vorhanden) und "Authorisierungs" Identitäten
abgespeichert werden. Anwendungen mappen oft Authentifizierungs Identitäten (z.B. Benutzernamen)
an Authorisierungs Identitäten (z.B. einer eindeutigen zugeordneten Integerzahl) wärend der
Authentifizierung, welche in der Zend_Auth Authentifizierungs Adapter Methode
authenticate()
stattfindet.
Beispiel 30.15. Beispiel: Einfacher Zugriff auf Authorisierungs Ids
<?php // Vor der Authentifizierungs Anfrage require_once 'Zend/Auth/Adapter/Digest.php'; $adapter = new Zend_Auth_Adapter_Digest($filename, $realm, $username, $password); $result = $adapter->authenticate(); require_once 'Zend/Session/Namespace.php'; $namespace = new Zend_Session_Namespace('Zend_Auth'); if ($result->isValid()) { $namespace->authorizationId = $result->getIdentity(); $namespace->date = time(); } else { $namespace->attempts++; } // Nachfolgende Anfragen require_once 'Zend/Session.php'; Zend_Session::start(); $namespace = new Zend_Session_Namespace('Zend_Auth'); echo "Gültig: ", (empty($namespace->authorizationId) ? 'Nein' : 'Ja'), "\n"'; echo "Authorisierung / Benutzer Id: ", (empty($namespace->authorizationId) ? 'keine' : print_r($namespace->authorizationId, true)), "\n"'; echo "Authentifizierungs Versuche: ", (empty($namespace->attempts) ? '0' : $namespace->attempts), "\n"'; echo "Authentifizierung am: ", (empty($namespace->date) ? 'Nein' : date(DATE_ATOM, $namespace->date), "\n"'; ?>
Authorisierungs Ids welche auf der Seite des Clienten gespeichert werden sind Angriffspunkte betreffend der Eskalation von Rechten wenn diese Ids verwendet und der Server diesen glaubt, solange bis zum Beispiel die Id auf Seite des Servers dupliziert (z.B. in der Session) und anschließend gegengeprüft wird mit der Authorisierungs Id welche durch den Client in der effektiven Session angefordert wird. Es wird unterschieden zwischen "Authentifizierungs Ids" (z.B. Benutzernamen) und "Authorisierungs Ids" (z.B. Benutzer ID #101 in der Benutzertabelle der Datenbank).
Das Spätere ist nicht unüblich aus Gründen der Geschwindigkeit, wie etwa Hilfe bei der Auswahl aus einem Pool von Servern welche Session Informationen holen um bei dem Henne-und-Ei Problemen zu helfen. Oft werden Debatten darüber geführt ob die echte Authorisierungs Id in einem Cookie verwendet wird, oder eine Ableitung welche in die echte Authorisierungs Id gemappt werden kann (oder Session oder Server welche die Benutzersession/Profile etc. enthalten), da einige Systemsicherheits Architekten wünschen das Verhindert wird das echte "Datenbank Primärschlüssel" nicht in die Wildnis entlassen werden. Diese Architekten versuchen einige Level von Verschleierung zu erreichen im Fall eines SQL Angriffs auf Ihr System. Nicht jeder verwendet automatisch-addierte Strategien für Authorisierungs Ids.
Der Zend Framework vertraut auf PHPUnit um das Testen von sich selbst zu ermöglichen. Viele
Entwickler erweitern die existierende Sammlung von Unit Tests um den Code in deren Anwendungen
anzudecken. Die Ausnahme "Zend_Session ist aktuell als nur-lesbar markiert"
wird geworfen wärend Unit Tests durchgeführt werden, wenn irgendeine schreibende Methode verwendet wird
nachdem Ende der Session. Trotzdem benötigen Unit Tests die Zend_Session verwenden besondere Aufmerksamkeit
weil das Schließen (Zend_Session::writeClose()
) oder Zerstören einer Session
(Zend_Session::destroy()
) weitere Änderungen oder Rücknahmen von Schlüsseln in jedem
Zend_Session_Namespace verhindert. Dieses Verhalten ist ein direktes Resultat des darunterliegenden
ext/session Mechanismus und PHP's session_destroy()
und session_write_close()
welche keinen "rückgängig machen" Mechanismus unterstützen um Setup/Teardown innerhalb der
Unit Tests zu unterstützen.
Um das Umzuarbeiten, siehe den Unit Test testSetExpirationSeconds()
in
tests/Zend/Session/SessionTest.php und SessionTestHelper.php
welche PHP's
exec()
verwenden um einen eigenständigen Prozess zu starten. Der neue Prozess
simuliert eine zweite Anfrage eines Browsers, viel genauer. Der separate Prozess beginnt mit einer
"reinen" Session, genauso wie jede PHP Skript Ausführung für eine web Anfrage. Auch jede Änderung in
$_SESSION[]
welche im aufrufenden Prozess gemacht wurde, ist im Kind-Prozess
verfügbar, ermöglicht wenn der Elternprozess die Session beendet hat, bevor exec()
verwendet wird.
Beispiel 30.16. Verwenden von PHPUnit um Code zu testen der Zend_Session* verwendet
<?php // testen von setExpirationSeconds() require 'tests/Zend/Session/SessionTestHelper.php'; // siehe auch SessionTest.php in trunk/ $script = 'SessionTestHelper.php'; $s = new Zend_Session_Namespace('space'); $s->a = 'apfel'; $s->o = 'orange'; $s->setExpirationSeconds(5); Zend_Session::regenerateId(); $id = Zend_Session::getId(); session_write_close(); // Session freigeben damit der untere Prozess Sie verwenden kann sleep(4); // nicht lange genug damit die Dinge ablaufen exec($script . "expireAll $id expireAll", $result); $result = $this->sortResult($result); $expect = ';a === apfel;o === orange;p === pfirsich'; $this->assertTrue($result === $expect, "Iterierung durch standard Zend_Session Namensraum fehlgeschlagen; erwartet result === '$expect', aber '$result' bekommen"); sleep(2); // lange genug damit die Dinge ablaufen (insgesamt 6 Sekunden warten, aber nach 5 Sekunden abgelaufen) exec($script . "expireAll $id expireAll", $result); $result = array_pop($result); $this->assertTrue($result === '', "Iterierung dirch standard Zend_Session Namensraum fehlgeschlagen; erwartet result === '', aber '$result' bekommen)"); session_start(); // wiederherstellen der vorher eingefrorenen Session // Das könnte in einen seperaten Test abgeteilt werden, aber aktuell, wenn irgendwas vom darüberleigenden // Test den darunterliegenden Test kontaminiert, ist das auch ein Fehler den wir wissen wollen. $s = new Zend_Session_Namespace('expireGuava'); $s->setExpirationSeconds(5, 'g'); // Versuch nur einen Schlüssel im Namensraum ablaufen zu lassen $s->g = 'guava'; $s->p = 'peach'; $s->p = 'plum'; session_write_close(); // Session auflösen damit der untere Prozess sie verwenden kann sleep(6); // Nicht lange genug damit die Dinge ablaufen können exec($script . "expireAll $id expireGuava", $result); $result = $this->sortResult($result); session_start(); // Die bestimmte Session wiederherstellen $this->assertTrue($result === ';p === plum', "Iterierung durch benannte Zend_Session Namensräume fehlgeschlaten (result=$result)"); ?>