
Artikel ini tidak masuk akal
tanpa bagian pertama , di mana ada jawaban "mengapa melakukan ini."
Ini adalah tentang teknik memigrasi proyek dengan lancar dari yii1 ke yii2. Esensinya adalah bahwa cabang-cabang proyek pada yii1 dan versi baru pada yii2 bekerja bersama pada domain yang sama dalam satu host virtual, dan migrasi dilakukan secara bertahap, dalam langkah-langkah kecil (melalui halaman, pengontrol, modul, dll.).
Bagian pertama adalah tentang cara menjalankan proyek telanjang pada yii2 di virtual host yang ada, yaitu membuat kedua cabang bekerja sama tanpa mengganggu satu sama lain.
Setelah itu tahap yang paling sulit secara psikologis dimulai: Anda perlu membuat infrastruktur minimal untuk memulai. Saya akan memilih 2 tugas: desain duplikat dan otentikasi pengguna ujung-ke-ujung.
Duplikasi desain diberikan tempat pertama dengan membosankan. Jika Anda kurang beruntung, Anda cukup menyalin / mengembalikan "1 in 1" yang lama. Secara pribadi, saya selalu dikombinasikan dengan desain ulang. Yaitu antarmuka dan desain telah diperbarui secara signifikan dan dalam hal ini, pekerjaannya tidak bodoh. Tapi di sini untuk masing-masing sendiri - saya membayar banyak perhatian pada antarmuka dan desain, seseorang, sebaliknya, lebih suka backend dan konsol. Namun demikian, terlepas dari preferensi, tidak ada cara untuk melewati tugas ini - Anda harus membuat antarmuka, dan jumlah pekerjaan akan cukup besar.
Otentikasi ujung ke ujung sedikit lebih menarik, dan akan ada lebih sedikit pekerjaan. Seperti pada artikel pertama, tidak akan ada wahyu. Karakter artikel: tutorial untuk mereka yang memecahkan masalah ini untuk pertama kalinya.
Jika ini adalah kasus Anda, maka lebih detail di bawah potongan
Pertama-tama, Anda perlu membagi fungsionalitas antara cabang-cabang. Karena Dipahami bahwa tahap migrasi hanya di awal, kemudian, kemungkinan besar, semua bekerja dengan pengguna (pendaftaran, otentikasi, pemulihan kata sandi, dll.) Tetap di situs lama. Dan cabang baru di yii2 seharusnya hanya melihat pengguna yang sudah diautentikasi.
Mekanisme otentikasi yii1 / yii2 sedikit berbeda dan Anda perlu mengubah kode yii2 agar dapat melihat pengguna yang diautentikasi. Karena data otentikasi disimpan dalam sesi, Anda hanya perlu menyetujui parameter untuk membaca data sesi.
Dalam sebuah sesi dari yii1, ini disimpan entah bagaimana seperti ini:
print_r($_SESSION); Array ( [34e60d27092d90364d1807021107e5a3__id] => 123456 [34e60d27092d90364d1807021107e5a3__name] => tester [34e60d27092d90364d1807021107e5a3__states] => Array ( ) )
Bagaimana cara disimpan dengan Anda - periksa, misalnya, prefixKey dihasilkan secara berbeda di versi yii1 yang berbeda.
Ini data yang Anda butuhkan dari yii1
Yii::app()->user->getStateKeyPrefix() Yii::app()->name Yii::app()->getBasePath() Yii::app()->getId() get_class(Yii::app()->user)
Cara termudah adalah membuat halaman pengujian dan menunjukkan padanya semua data yang diperlukan - mereka akan dibutuhkan di masa depan.
Otentikasi di yii2
Di Yii2, semua fungsi yang kita butuhkan terletak di komponen pengguna (
\ yii \ web \ User ), yang mengontrol keadaan otentikasi.
- Dalam metode getIdentity () , renewAuthStatus () dipanggil, di mana sesi otentikasi dilihat oleh kunci dari variabel $ idParam (secara default '__id' disimpan di sana);
- Dalam variabel sesi, id pengguna disimpan menggunakan kunci $ idParam (misalnya, dari aplikasi / model / Pengguna ).
Algoritme otentikasi dijelaskan secara rinci dalam panduan resmi .Tentu saja, pada yii1, sesi disimpan dengan kunci yang berbeda. Oleh karena itu, Anda perlu membuat yii2 mencari ID pengguna menggunakan kunci yang sama dengan yang disimpan di yii1.
Untuk melakukan ini:1. Ubah kelas komponen pengguna yang bertanggung jawab untuk mengelola negara otentikasi ke kita sendiri, diwarisi dari
yii / web / Pengguna di
config / web.php 'components' => [ 'user' => [ 'class' => 'app\models\WebUser', 'identityClass' => 'app\models\User', ], ]
2. Sesuaikan nilai
$ idParam di
app \ models \ WebUser .
public function init() {
Di bawah spoiler akan ada beberapa metode yang entah bagaimana meniru perilaku serupa dari yii1.
Secara umum, Anda bisa menyalin
_keyPrefix asli (atau bahkan
idParam segera ) dari yii1 dan tidak meniru generasinya, tetapi kemudian akan seperti instruksi "menyalin sampah yang tidak bisa dimengerti".
Memang, Anda dapat menyalin karena _keyPrefix di yii1 hampir statis. Itu tergantung pada nama kelas dari komponen pengguna dan pada ID aplikasi, yang pada gilirannya diperoleh dari lokasi aplikasi dan namanya.
Jika kami membatasi diri hanya pada tugas otentikasi, menyalin nilai _keyPrefix secara signifikan mengurangi jumlah pekerjaan. Tetapi saya akan memiliki contoh untuk penggunaan yang lebih luas.
Komponen pengguna (app \ models \ WebUser) namespace app\models; use yii\web\User; class WebUser extends User { public $autoRenewCookie = false; private $_keyPrefix; private $paramsYii1 = [
Dan metode tambahan untuk itu (WebUser).
Dipisahkan agar mudah dilihat. 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; }
Hanya untuk tugas " menyetujui format kunci sesi " dekomposisi metode sedikit rumit, tetapi mereka berguna untuk contoh di bawah ini. Setelah itu, di cabang baru di yii2, pengakuan pengguna yang sebelumnya diotorisasi di yii1 mulai bekerja. Idealnya, akan perlu untuk berhenti pada ini, karena jalan yang licin dimulai lebih jauh.
Login pengguna di yii2
Setelah format penyimpanan untuk ID pengguna dalam sesi disepakati, ada kemungkinan bahwa bahkan "secara otomatis" login Pengguna akan bekerja melalui yii2.
Saya menganggap itu salah untuk menggunakan formulir login pada yii2 dalam mode pertempuran tanpa menonaktifkan formulir yang sesuai pada yii1, namun, fungsi dasar akan bekerja setelah sedikit koordinasi.Karena Kami percaya bahwa sementara pendaftaran pengguna, dan dengan demikian hashing kata sandi, tetap di yii1, untuk login yang masuk melalui yii2 kita perlu memastikan bahwa metode validasi kata sandi yang disimpan di yii2 dapat memahami apa yang hash dan disimpan dalam Yii1.
Periksa apa yang dilakukan metode ini.
Misalnya, jika dalam Yii2, model pengguna bersyarat standar memvalidasi kata sandi sebagai berikut:
public function validatePassword($password) { return \Yii::$app->getSecurity()->validatePassword($password, $this->password); }
Kemudian, lihat metode
validatePassword ($ password, $ hash) dari
yii \ base \ Security (Yii2)
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); }
Dan jika pada Yii1 kata sandi hashing dalam model Pengguna dilakukan seperti ini:
public function hashPassword($password) { return CPasswordHelper::hashPassword($password); }
Kemudian bandingkan dengan
memverifikasiPassword ($ password, $ hash) dari
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; }
Jika metode hashing dan validasi berbeda, maka Anda perlu mengubah validasi di
validatePassword () dari
app \ model \ User .
Dari kotak versi terbaru kerangka kerja, hash kata sandi Yii1 / Yii2 kompatibel. Tetapi ini sama sekali tidak berarti bahwa mereka akan kompatibel dengan Anda atau bahwa itu akan bertepatan di masa depan. Dengan tingkat probabilitas yang tinggi, metode hashing proyek di Yii1 dan validasi dalam proyek baru di Yii2 akan berbeda.
Autologin di Yii2 pada cookie dari yii1
Karena cabang di Yii2 sudah tahu cara menggunakan data otentikasi pengguna secara transparan dari Yii1, mengapa tidak mengatur login otomatis dengan cookie?
Jika pikiran seperti itu menimpa Anda, maka saya menyarankan Anda untuk meninggalkannya. Saya tidak melihat alasan yang baik untuk mengaktifkan autologin di Yii2 tanpa mentransfer pekerjaan pengguna (otentikasi, pertama-tama) ke utas ini. Yaitu Maksud saya kasus berikut:
otentikasi pengguna dilakukan pada Yii1, tetapi cabang Yii2 harus dapat melakukan autologin pada cookie yang disimpan dalam Yii1.
Sejujurnya, ini adalah penyimpangan jujur. Tidak mudah dan elegan untuk dilakukan, dan hanya di sini garis melewati antara tugas migrasi yang disadari dan dibenarkan serta penemuan sepeda yang tidak perlu.
Kesulitannya adalah bahwa di kedua cabang Yii dilindungi dari cookie palsu, sehingga sulit untuk menyetujui metode.
- Komponen yang terlibat dalam Yii2 adalah: pengguna ( \ yii \ web \ Pengguna ), Permintaan , Keamanan + CookieCollection
- Di Yii1: CWebUser , CHttpRequest , CSecurityManager , CStatePersister , CCookieCollection
Namun, kasusnya berbeda. Di bawah ini adalah contoh cara membuat autologin dengan sepeda.
Pada yii2, kami tertarik dengan metode
getIdentityAndDurationFromCookie () dari
\ yii \ web \ User . Pada baris pertama metode ini, cookie yang diinginkan harus diperoleh:
$value = Yii::$app->getRequest()->getCookies()->getValue($this->identityCookie['name']);
Tetapi dia tidak akan, karena koleksi
Yii :: $ app-> getRequest () -> getCookies () akan kosong, karena di Cookie
permintaan dimuat dengan validasi di
loadCookies () dan, tentu saja, itu tidak lulus.
Cara termudah untuk melakukan fork adalah perilaku standar dengan menimpa
getIdentityAndDurationFromCookie () . Misalnya, seperti ini:
- Unduh cookie yang diinginkan langsung dari superglobal $ _COOKIE agar tidak
melanggar adaptasi mekanisme pemuatan cookie standar.
Nama cookie identifikasi hanyalah _keyPrefix, yang sudah kami ketahui cara menerima (atau menyalin). Karenanya, kami mengubah standar $ identityCookie di init () .
- Dekripsi cookie yang dihasilkan "kira-kira seperti pada yii1." Terserah Anda. Sebagai contoh, saya menyalin metode yang diperlukan dari CSecurityManager .
Sebenarnya, di bawah ini adalah kodenya.
Kami bekerja di aplikasi / model / WebUser1. Nama cookie cookie ditetapkan sesuai dengan yii1
public function init() { $this->idParam = $this->getIdParamYii1();
2. Tambahkan dua metode lagi
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']];
Kode ini menggunakan kelas
UtilYii1Security tertentu - ini adalah salinan-tempel yang dimodifikasi dari metode yang diperlukan dari
CSecurityManager , sehingga di satu sisi terlihat seperti aslinya, tetapi dengan penyederhanaan. Misalnya, di
CSecurityManager ada beberapa opsi untuk menghasilkan HMAC (kode otentikasi pesan berbasis hash), yang bergantung pada versi php dan keberadaan mbstring. Tapi sejak itu Diketahui bahwa yii1 bekerja di lingkungan yang sama dengan yii2, maka tugasnya disederhanakan dan, karenanya, kodenya juga.
Karena cukup jelas bahwa penopang eksplisit ditulis di sini, maka tidak perlu mencoba membuatnya universal dan memberikan bentuk yang anggun, cukup untuk "memenjarakan" itu di bawah kondisi Anda.
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; }
Urutan tindakan
Saat bermigrasi dari yii1 ke yii2 dalam hal otentikasi, saya mengikuti urutan berikut:
- Buat otentikasi pengguna transparan antar cabang.
Yaitu sehingga cabang yii2 menerima pengguna yang diautentikasi dengan yii1. Cepat, tidak sulit.
- Otentikasi transfer (login pengguna) dari yii1 ke yii2.
Menonaktifkannya sekaligus di cabang lama. Perhatikan bahwa setelah ini autologin untuk cookie akan berhenti berfungsi, karena cookie dari yii1 tidak lagi cocok, dan masih ada beberapa halaman baru di yii2. - Port setidaknya halaman utama situs ke yii2
Sehingga Anda dapat menggunakan autologin untuk cookie baru yang disimpan di yii2.
Kehadiran autologin, setidaknya pada yang utama, akan membantu untuk menutupi autologin yang hilang pada cabang sebelumnya.
- Periksa bahwa yii1 memahami yang diautentikasi dalam yii2.
Melalui negosiasi kunci sesi.
- Transfer pendaftaran pengguna ke yii2.
Transfer harus dilakukan dengan koordinasi hash kata sandi yang tersimpan sebelumnya. Mungkin menyimpan format hash lama atau memperkenalkan yang baru, tetapi agar login memahami kedua jenis. - Pertimbangkan untuk menambahkan pengguna ke situs layanan yang memberikan yii2 “out of the box”.
Maksud saya implementasi antarmuka IdentityInterface Pengguna, yang memungkinkan otentikasi dengan token, pemulihan kata sandi, dll. Mungkin Anda sudah memiliki baju zirah yang sesuai, tetapi tiba-tiba tidak? Maka ini adalah pilihan bagus untuk meningkatkan layanan dengan upaya minimal.
Jika ya, maka ini akan mengarah pada implementasi (migrasi) akun pribadi di yii2 (setidaknya sebagian).
Jika "tidak", maka Anda harus tetap berpikir tentang migrasi akun pribadi Anda (bahkan tanpa produk baru).
PS:
Itu tidak selalu menggambarkan solusi yang tidak ambigu dalam tugas tertentu dan tidak semuanya perlu diterapkan.
Mereka tidak dideskripsikan dengan tujuan mengatakan "lakukan seperti yang saya lakukan." Misalnya, membuat yii2 autologin untuk cookie dari yii1 adalah mungkin, tetapi, secara sederhana, tidak baik (dan penopang semacam itu harus dibenarkan dalam beberapa cara).
Tapi saya sudah menghabiskan waktu untuk migrasi proyek ini selangkah demi selangkah dan akan senang jika seseorang, melihat pengalaman saya, menyelamatkannya.