
Dieser Artikel ist
ohne den ersten Teil nicht sinnvoll, in dem die Antwort "Warum tun?"
Es geht um die Technik der reibungslosen Migration eines Projekts von yii1 nach yii2. Das Wesentliche ist, dass die Zweige des Projekts auf yii1 und seine neue Version auf yii2 auf derselben Domäne in einem virtuellen Host zusammenarbeiten und die Migration schrittweise in kleinen Schritten (über Seiten, Controller, Module usw.) durchgeführt wird.
Im ersten Teil ging es darum, wie ein nacktes Projekt auf yii2 in einem vorhandenen virtuellen Host ausgeführt wird, d. H. Lassen Sie beide Zweige zusammenarbeiten, ohne sich gegenseitig zu stören.
Danach beginnt die psychologisch schwierigste Phase: Sie müssen für den Start eine minimale Infrastruktur erstellen. Ich würde zwei Aufgaben herausgreifen: doppeltes Design und End-to-End-Benutzerauthentifizierung.
Die Vervielfältigung des Designs wird durch Langeweile an erster Stelle gestellt. Wenn Sie Pech haben, können Sie einfach das alte "1 in 1" kopieren / zurücksetzen. Persönlich habe ich immer mit dem Redesign kombiniert. Das heißt, Die Benutzeroberfläche und das Design wurden erheblich aktualisiert und in dieser Hinsicht ist die Arbeit nicht dumm. Aber hier für jeden sein eigenes - ich achte sehr auf die Benutzeroberfläche und das Design, im Gegenteil, jemand mag das Backend und die Konsole mehr. Unabhängig von den Einstellungen gibt es jedoch keine Möglichkeit, an dieser Aufgabe vorbeizukommen. Sie müssen eine Benutzeroberfläche erstellen, und der Arbeitsaufwand ist recht hoch.
Die End-to-End-Authentifizierung ist etwas interessanter und es wird weniger Arbeit geben. Wie im ersten Artikel wird es keine Enthüllungen geben. Charakter des Artikels: Tutorial für diejenigen, die dieses Problem zum ersten Mal lösen.
Wenn dies Ihr Fall ist, dann mehr Details unter dem Schnitt
Zunächst müssen Sie die Funktionalität auf die Zweige aufteilen. Weil Es versteht sich, dass sich die Migrationsphase erst am Anfang befindet und dann höchstwahrscheinlich alle Arbeiten mit Benutzern (Registrierung, Authentifizierung, Kennwortwiederherstellung usw.) auf der alten Site verbleiben. Und der neue Zweig auf yii2 sollte nur bereits authentifizierte Benutzer sehen.
Die Authentifizierungsmechanismen von yii1 / yii2 unterscheiden sich geringfügig, und Sie müssen den yii2-Code so anpassen, dass ein authentifizierter Benutzer angezeigt wird. Weil Authentifizierungsdaten werden in der Sitzung gespeichert. Sie müssen lediglich die Parameter für das Lesen der Sitzungsdaten festlegen.
In einer Sitzung von yii1 wird dies irgendwie wie folgt gespeichert:
print_r($_SESSION); Array ( [34e60d27092d90364d1807021107e5a3__id] => 123456 [34e60d27092d90364d1807021107e5a3__name] => tester [34e60d27092d90364d1807021107e5a3__states] => Array ( ) )
Wie wird es bei Ihnen gespeichert? Überprüfen Sie beispielsweise, ob prefixKey in verschiedenen Versionen von yii1 unterschiedlich generiert wird.
Hier sind die Daten, die Sie von yii1 benötigen
Yii::app()->user->getStateKeyPrefix() Yii::app()->name Yii::app()->getBasePath() Yii::app()->getId() get_class(Yii::app()->user)
Am einfachsten ist es, eine Testseite zu erstellen und alle erforderlichen Daten darauf anzuzeigen - sie werden in Zukunft benötigt.
Authentifizierung in yii2
In Yii2 befindet sich die gesamte benötigte Funktionalität in der Benutzerkomponente (
\ yii \ web \ User ), die den Authentifizierungsstatus steuert.
- In der Methode getIdentity () wird erneuerAuthStatus () aufgerufen, in der die Authentifizierungssitzung vom Schlüssel aus der Variablen $ idParam nachgeschlagen wird (standardmäßig wird dort '__id' gespeichert).
- In der Sitzungsvariablen wird die Benutzer-ID mit dem Schlüssel $ idParam gespeichert (z. B. aus App / Modell / Benutzer ).
Der Authentifizierungsalgorithmus ist im offiziellen Handbuch ausführlich beschrieben .In yii1 werden Sitzungen natürlich mit einem anderen Schlüssel gespeichert. Daher müssen Sie yii2 veranlassen, die Benutzer-ID mit denselben Schlüsseln zu suchen, mit denen sie in yii1 gespeichert ist.
Dafür:1. Ändern Sie die Klasse der Benutzerkomponente, die für die Verwaltung des Authentifizierungsstatus verantwortlich ist, in unsere eigene, die von
yii / web / User in
config / web.php geerbt wurde 'components' => [ 'user' => [ 'class' => 'app\models\WebUser', 'identityClass' => 'app\models\User', ], ]
2. Passen Sie den Wert von
$ idParam in
app \ models \ WebUser an .
public function init() {
Unter dem Spoiler gibt es einige Methoden, die ein ähnliches Verhalten von yii1 emulieren.
Im Allgemeinen könnte man einfach das ursprüngliche
_keyPrefix (oder sogar
idParam sofort ) von yii1 kopieren und seine Generierung nicht emulieren, aber dann wäre es wie die Anweisung "Kopieren von unverständlichem Müll".
Sie können tatsächlich kopieren, da _keyPrefix in yii1 fast statisch ist. Dies hängt vom Klassennamen der Benutzerkomponente und von der Anwendungs-ID ab, die wiederum vom Speicherort der Anwendung und ihrem Namen abgerufen wird.
Wenn wir uns nur auf die Authentifizierungsaufgabe beschränken, reduziert das Kopieren des _keyPrefix-Werts den Arbeitsaufwand erheblich. Aber ich werde Beispiele für eine breitere Verwendung haben.
Benutzerkomponente (app \ models \ WebUser) namespace app\models; use yii\web\User; class WebUser extends User { public $autoRenewCookie = false; private $_keyPrefix; private $paramsYii1 = [
Und zusätzliche Methoden dazu (WebUser).
Für einfache Anzeige getrennt. public function getIdParamYii1() { return $this->getStateKeyPrefix() . '__id'; } public function getStateKeyPrefix() { if ($this->_keyPrefix !== null) return $this->_keyPrefix; $class = $this->paramsYii1['classUserComponent']; return $this->_keyPrefix = md5('Yii.' . $class . '.' . $this->getAppIdYii1()); } public function getAppIdYii1() { if ($this->paramsYii1['appId']) return $this->paramsYii1['appId']; return $this->paramsYii1['appId'] = sprintf('%x', crc32($this->getBasePathYii1() . $this->paramsYii1['appName'])); } private function getBasePathYii1() { $basePath = realpath(\Yii::getAlias('@app') . DIRECTORY_SEPARATOR . $this->paramsYii1['relPath']); if (!$basePath) throw new InvalidConfigException('basePath yii1 .'); return $basePath; }
Nur für die Aufgabe, " das Format des Sitzungsschlüssels zu vereinbaren ", ist die Zerlegung der Methoden etwas kompliziert, aber sie sind für die folgenden Beispiele nützlich. Danach beginnt in einem neuen Zweig auf yii2 die Erkennung von Benutzern, die zuvor in yii1 autorisiert wurden, zu funktionieren. Im Idealfall müsste hier angehalten werden, da der rutschige Weg weiter beginnt.
Benutzeranmeldung in yii2
Nachdem das Speicherformat für die Benutzer-ID in der Sitzung vereinbart wurde, ist es möglich, dass die Benutzeranmeldung sogar „automatisch“ über yii2 funktioniert.
Ich halte es für falsch, das Anmeldeformular auf yii2 im Kampfmodus zu verwenden, ohne das entsprechende Formular auf yii1 zu deaktivieren. Die Grundfunktionalität funktioniert jedoch nach ein wenig Koordination.Weil Wir glauben, dass während die Benutzerregistrierung und das entsprechende Passwort-Hashing in yii1 verbleiben, wir für eine funktionierende Anmeldung über yii2 sicherstellen müssen, dass die Validierungsmethode des in yii2 gespeicherten Passworts verstehen kann, was in Yii1 gehasht und gespeichert wurde.
Überprüfen Sie, was diese Methoden tun.
Wenn in Yii2 beispielsweise das Benutzermodell mit bedingtem Standardmodell das Kennwort wie folgt überprüft:
public function validatePassword($password) { return \Yii::$app->getSecurity()->validatePassword($password, $this->password); }
Schauen Sie sich dann die
validatePassword- Methode ($ password, $ hash) unter
yii \ base \ Security (Yii2) an.
validatePassword () public function validatePassword($password, $hash) { if (!is_string($password) || $password === '') { throw new InvalidArgumentException('Password must be a string and cannot be empty.'); } if (!preg_match('/^\$2[axy]\$(\d\d)\$[\.\/0-9A-Za-z]{22}/', $hash, $matches) || $matches[1] < 4 || $matches[1] > 30 ) { throw new InvalidArgumentException('Hash is invalid.'); } if (function_exists('password_verify')) { return password_verify($password, $hash); } $test = crypt($password, $hash); $n = strlen($test); if ($n !== 60) { return false; } return $this->compareString($test, $hash); }
Und wenn auf Yii1 das Passwort-Hashing im Benutzermodell wie folgt durchgeführt wird:
public function hashPassword($password) { return CPasswordHelper::hashPassword($password); }
Vergleichen Sie dann mit
verifyPassword ($ password, $ hash) aus
yii \ framework \ utils \ CPasswordHelperhashPassword () public static function hashPassword($password,$cost=13) { self::checkBlowfish(); $salt=self::generateSalt($cost); $hash=crypt($password,$salt); if(!is_string($hash) || (function_exists('mb_strlen') ? mb_strlen($hash, '8bit') : strlen($hash))<32) throw new CException(Yii::t('yii','Internal error while generating hash.')); return $hash; }
Wenn sich die Hashing- und Validierungsmethoden unterscheiden, müssen Sie die Validierung in
validatePassword () von
app \ model \ User aus ändern.
Aus den Feldern der neuesten Versionen des Frameworks sind Yii1 / Yii2-Kennwort-Hashes kompatibel. Dies bedeutet jedoch keineswegs, dass sie mit Ihnen kompatibel sind oder in Zukunft zusammenfallen werden. Mit hoher Wahrscheinlichkeit unterscheiden sich die Hashing-Methoden des Projekts in Yii1 und die Validierung im neuen Projekt in Yii2.
Autologin in Yii2 auf Cookies von yii1
Da ein Zweig auf Yii2 bereits weiß, wie Benutzerauthentifizierungsdaten von Yii1 transparent verwendet werden, können Sie die automatische Anmeldung per Cookie einrichten.
Wenn Ihnen ein solcher Gedanke einfällt, rate ich Ihnen, ihn aufzugeben. Ich sehe keinen guten Grund, die automatische Anmeldung auf Yii2 zu aktivieren, ohne die Benutzerarbeit (zunächst die Authentifizierung) auf diesen Thread zu übertragen. Das heißt, Ich meine den folgenden Fall:
Die Benutzerauthentifizierung wird für Yii1 durchgeführt, aber der Yii2-Zweig sollte in der Lage sein, die in Yii1 gespeicherten Cookies automatisch zu registrieren.
Um ehrlich zu sein, ist dies eine offene Perversion. Es wird nicht einfach und elegant sein, und genau hier geht die Grenze zwischen der bewussten und gerechtfertigten Aufgabe der Migration und der Erfindung unnötiger Fahrräder.
Die Schwierigkeit besteht darin, dass Yii in beiden Zweigen vor gefälschten Cookies geschützt ist, so dass es schwierig ist, Methoden zu vereinbaren.
- Die an Yii2 beteiligten Komponenten sind: Benutzer ( \ yii \ web \ Benutzer ), Anforderung , Sicherheit + CookieCollection
- In Yii1: CWebUser , CHttpRequest , CSecurityManager , CStatePersister , CCookieCollection
Die Fälle sind jedoch unterschiedlich. Unten finden Sie ein Beispiel für die Erstellung eines Autologins mit Fahrrädern.
In yii2 interessieren wir uns für die Methode
getIdentityAndDurationFromCookie () aus
\ yii \ web \ User . In der ersten Zeile dieser Methode sollte der gewünschte Cookie erhalten werden:
$value = Yii::$app->getRequest()->getCookies()->getValue($this->identityCookie['name']);
Aber sie wird es nicht sein, weil Die Sammlung
Yii :: $ app-> getRequest () -> getCookies () ist leer, da in
Request Cookies mit der Validierung in
loadCookies () geladen werden und natürlich nicht bestanden werden.
Der einfachste Weg zum
Verzweigen ist das Standardverhalten durch Überschreiben von
getIdentityAndDurationFromCookie () . Zum Beispiel so:
- Laden Sie das gewünschte Cookie direkt vom superglobalen $ _COOKIE herunter
, um den Standardmechanismus zum Laden von Cookies nicht zu beschädigen.
Der Name des Identifikationscookies ist nur _keyPrefix, das wir bereits empfangen (oder kopieren) können. Daher ändern wir den Standard $ identityCookie in init () .
- Entschlüsseln Sie das resultierende Cookie "ungefähr wie in yii1". Wie du willst. Zum Beispiel habe ich die erforderlichen Methoden aus CSecurityManager kopiert.
Unten ist in der Tat der Code.
Wir arbeiten in App / Models / WebUser1. Der gemäß yii1 gesetzte Name identityCookie-Cookies
public function init() { $this->idParam = $this->getIdParamYii1();
2. Fügen Sie zwei weitere Methoden hinzu
protected function getIdentityAndDurationFromCookie() { $id = $this->getIdIdentityFromCookiesYii1(); if (!$id) { return null; } $class = $this->identityClass; $identity = $class::findOne($id); if ($identity !== null) { return ['identity' => $identity, 'duration' => 0]; } return null; } protected function getIdIdentityFromCookiesYii1() { if (!isset($_COOKIE[$this->identityCookie['name']])) return null; $cookieValue = $_COOKIE[$this->identityCookie['name']];
Der Code verwendet eine bestimmte
UtilYii1Security- Klasse - dies ist ein modifiziertes Kopieren und Einfügen der erforderlichen Methoden aus
CSecurityManager , sodass es einerseits wie das Original aussieht, jedoch mit Vereinfachungen. In
CSecurityManager gibt es beispielsweise verschiedene Optionen zum Generieren von HMAC (Hash-basierter Nachrichtenauthentifizierungscode), die von der PHP-Version und dem Vorhandensein von mbstring abhängen. Aber seitdem Es ist bekannt, dass yii1 in derselben Umgebung wie yii2 arbeitet, dann wird die Aufgabe vereinfacht und dementsprechend auch der Code.
Weil Es ist ziemlich klar, dass hier eine explizite Krücke geschrieben ist. Dann müssen Sie nicht versuchen, sie universell zu machen und ihr eine anmutige Form zu geben. Es reicht aus, sie unter Ihren Bedingungen „einzusperren“.
UtilYii1Security.php <?php namespace app\components; use yii\base\Exception; use yii\base\Model; use yii\base\InvalidConfigException; class UtilYii1Security { const STATE_VALIDATION_KEY = 'Yii.CSecurityManager.validationkey'; public $hashAlgorithm = 'sha1'; private $_validationKey; private $basePath; private $stateFile; public function __construct($basePath) { $this->basePath = $basePath; $this->stateFile = $this->basePath . DIRECTORY_SEPARATOR . 'runtime' . DIRECTORY_SEPARATOR . 'state.bin'; if (!realpath($this->stateFile)) throw new InvalidConfigException(' '); } public function validateData($data, $key = null) { if (!is_string($data)) return false; $len = $this->strlen($this->computeHMAC('test')); if ($this->strlen($data) >= $len) { $hmac = $this->substr($data, 0, $len); $data2 = $this->substr($data, $len, $this->strlen($data)); return $this->compareString($hmac, $this->computeHMAC($data2, $key)) ? $data2 : false; } else return false; } public function computeHMAC($data, $key = null) { if ($key === null) $key = $this->getValidationKey(); return hash_hmac($this->hashAlgorithm, $data, $key); } public function getValidationKey() { if ($this->_validationKey !== null) return $this->_validationKey; if (($key = $this->loadStateValidationKey(self::STATE_VALIDATION_KEY)) !== null) { $this->_validationKey = $key; } return $this->_validationKey; }
Reihenfolge der Aktionen
Bei der Migration von yii1 nach yii2 hinsichtlich der Authentifizierung habe ich die folgende Reihenfolge befolgt:
- Machen Sie eine transparente Benutzerauthentifizierung zwischen Zweigen.
Das heißt, Damit akzeptiert der Zweig yii2 Benutzer, die mit yii1 authentifiziert sind. Es ist schnell, nicht schwierig.
- Übertragen Sie die Authentifizierung (Benutzeranmeldung) von yii1 nach yii2.
Gleichzeitig im alten Zweig deaktivieren. Beachten Sie, dass danach die Autologin für Cookies nicht mehr funktioniert, weil Cookies von yii1 sind nicht mehr geeignet und es gibt noch wenige neue Seiten auf yii2. - Portieren Sie mindestens die Hauptseite der Site auf yii2
Damit Sie das Autologin für die neuen Cookies verwenden können, die in yii2 gespeichert sind.
Das Vorhandensein eines Autologins, zumindest des Hauptautologins, hilft dabei, das fehlende Autologin im vorherigen Zweig zu maskieren.
- Überprüfen Sie, ob yii1 die Authentifizierung in yii2 versteht.
Durch Aushandlung von Sitzungsschlüsseln.
- Übertragen Sie die Benutzerregistrierung auf yii2.
Die Übertragung muss unter Koordination der zuvor gespeicherten Passwort-Hashes erfolgen. Behalten Sie möglicherweise das alte Hash-Format bei oder führen Sie ein neues ein, damit der Login beide Typen versteht. - Erwägen Sie, der Site einen Dienst hinzuzufügen, der yii2 "out of the box" bietet.
Ich meine die Implementierung der IdentityInterface- Oberfläche des Benutzers, die die Authentifizierung durch Token, die Wiederherstellung von Passwörtern usw. ermöglicht. Vielleicht haben Sie schon das passende Geschirr, aber plötzlich nicht mehr? Dann ist dies eine großartige Option, um den Service mit minimalem Aufwand zu verbessern.
Wenn ja, führt dies (zumindest teilweise) zur Implementierung (Migration) des persönlichen Kontos in yii2.
Wenn "Nein", sollten Sie immer noch über die Migration Ihres persönlichen Kontos nachdenken (auch ohne neue Produkte).
PS:
Es werden nicht immer eindeutige Lösungen für eine bestimmte Aufgabe beschrieben, und nicht alle müssen angewendet werden.
Sie werden nicht beschrieben, um zu sagen: "Tu, was ich tue." Zum Beispiel ist es möglich, yii2-Autologin für Cookies von yii1 zu erstellen, aber gelinde gesagt nicht gut (und eine solche Krücke sollte in irgendeiner Weise gerechtfertigt sein).
Aber ich habe bereits Zeit mit dieser schrittweisen Migration von Projekten verbracht und werde mich freuen, wenn jemand, der meine Erfahrungen betrachtet, ihn rettet.