原地址:http://blog.csdn.net/a553181867/article/details/50987388
最近在利用Yii 2.0框架進行項目后台的編寫,遇到的第一個問題是用戶登陸,包括利用cookie,session登陸等等,筆者從源碼角度結合實例為各位詳細解析如何編寫一個完整的用戶登陸模塊。(筆者的本地環境是PHP 5.5+MySQL5.6)
一、准備
二、模型(Model)
Yii框架采用MVC設計模式,所以Model是一個模塊的核心所在,所以我們先完成對Model的編寫。
1、LoginForm.php
用戶登陸模塊,所提交的是username和password,所以我們要先建立一個Model,專門處理用戶提交的數據,所以先新建一個LoginForm.php,以下為代碼:
- <?php
- namespace app\modules\backend\models;
- use Yii;
- use yii\base\Model;
- /**
- * LoginForm is the model behind the login form.
- */
- class LoginForm extends Model
- {
- public $username;
- public $password;
- public $rememberMe = true;
- private $_user = false;
- /**
- * @return array the validation rules.
- */
- public function rules()<span style="white-space:pre"> </span>//①
- {
- return [
- // username and password are both required
- [['username', 'password'], 'required','message'=>""],
- // rememberMe must be a boolean value
- ['rememberMe', 'boolean'],
- // password is validated by validatePassword()
- ['password', 'validatePassword'],
- ];
- }
- /**
- * Validates the password.
- * This method serves as the inline validation for password.
- *
- * @param string $attribute the attribute currently being validated
- * @param array $params the additional name-value pairs given in the rule
- */
- public function validatePassword($attribute, $params)
- {
- if (!$this->hasErrors()) {
- $user = $this->getUser();
- if (!$user || !$user->validatePassword($this->password)) {
- $this->addError($attribute, 'Incorrect username or password.');
- }
- }
- }
- /**
- * Logs in a user using the provided username and password.
- * @return boolean whether the user is logged in successfully
- */
- public function login()
- {
- if ($this->validate()) {
- if($this->rememberMe)
- {
- $this->_user->generateAuthKey();//③
- }
- return Yii::$app->user->login($this->getUser(), $this->rememberMe ? 3600*24*30 : 0);
- }
- return false;
- }
- /**
- * Finds user by [[username]]
- *
- * @return User|null
- */
- public function getUser()
- {
- if ($this->_user === false) {
- $this->_user = User::findByUsername($this->username); //②
- }
- return $this->_user;
- }
- }
該Model是根據basic模板自帶的LoginForm修改而成,代碼中大多有注釋,這里關注以下代碼:
①號代 碼處是rules規則,rules規則定義了填充過來的數據的規則,驗證所填的數據是否為空,是否符合格式之類的,其中有一欄是password,對應的 規則是validatePassword,會自動調用當前類的validatePassword()方法,注意與下文的User類對應的方法區分。
②號代碼,調用了User類里面的findByUsername方法,這個User類下面會寫到,主要是為了返回一個AR類實例,與當前LoginForm的數據進行比較。
③號代碼,這里暫時不提,等講到cookie登陸的時候再提。
2、User.php
(1)ActiveRecord 類
在 完成LoginForm后,我們還缺少一些東西,從用戶接受到數據了,那么還需要從數據庫取出相應的數據來進行比較,所以我們接下來需要完成的是一個從數 據庫獲取的數據的類——AR類,全稱是ActiveRecord,活動記錄類,方便用於查找數據,只要類名和數據表的表名相同,那么它就能從這個數據表中 獲取數據,比如說這樣:
- <?php
- namespace app\modules\backend\models;
- use yii\db\ActiveRecord;
- class User extends ActiveRecord{ } ?>
此外,還能自己添加返回的表名,只要在這個類中重寫以下方法:
- public static function tableName(){
- return 'user';
- }
(2)IdentityInterface 接口
一 般來說,從數據庫查找數據,只需要繼承AR類即可,但是,我們這個是用戶登錄模型,核心是驗證,所以自然需要實現核心的驗證功能,就像LoginForm 模型提到的validatePassword一樣,實際的驗證邏輯是在當前的User模型完成的。一般來說,實現IdentityInterface接 口,需要實現以下方法:
- public static function findIdentity($id); //①
- public static function findIdentityByAccessToken($token, $type = null); //②
- public function getId(); //③
- public function getAuthKey(); //④
- public function validateAuthKey($authKey); //⑤
①findIdentity:是根據id查找數據表對應的數據
②findIdentityByAccessToken 是根據AccessToken(上文提到的)查找對應的數據,而AccessToken我們在數據表也有這個字段,那么它到底有什么用呢?其實 AccessToken在我們當前的用戶登陸模型中用處並不大,它是專門用於Resetful登陸驗證用到的,具體可自行百度,這里不展開說明。
③getId:返回當前AR類所對應的id
④getAuthKey:返回當前AR類所對應的auth_key
⑤validateAuthKey:這個方法比較重要,是我們后面要講到的cookie登陸驗證的核心所在。
好了,既然知道了這五個方法的用處,那么我們在我們的User.php實現接口,然后重寫以上方法,完整的User.php的代碼如下:
- <?php
- namespace app\modules\backend\models;
- use yii\db\ActiveRecord;
- class User extends ActiveRecord implements \yii\web\IdentityInterface
- {
- public static function tableName(){
- return 'user';
- }
- public static function findIdentity($id){
- return static::findOne($id);
- }
- public static function findIdentityByAccessToken($token,$type=null){
- return static::findOne(['accessToken'=>$token]);
- }
- public static function findByUsername($username){ //①
- return static::findOne(['username'=>$username]);
- }
- public function getId(){
- return $this->id;
- }
- public function getAuthkey(){
- return $this->auth_key;
- }
- public function validateAuthKey($authKey){
- return $this->auth_key === $authKey;
- }
- public function validatePassword($password){ //②
- return $this->password === md5($password);
- }
- <span style="white-space:pre"> </span> /**
- <span style="white-space:pre"> </span> * Generates "remember me" authentication key
- <span style="white-space:pre"> </span> */
- public function generateAuthKey() //③
- {
- <span style="white-space:pre"> </span>$this->auth_key = \Yii::$app->security->generateRandomString();
- <span style="white-space:pre"> </span>$this->save();
- }
- }
- ?>
這里分析其中的三個方法:
①findByUsername():在LoginForm的代碼中,引用了這個方法,目的是根據用戶提交的username返回一個在數據表與username相同的數據項,即AR實例。
②validatePassword():這里對用戶提交的密碼以及當前AR類的密碼進行比較。
③generateAuthKey():生成隨機的auth_key,用於cookie登陸。
到此,我們完成了Model的編寫,一共寫了兩個Model類:LoginForm和User,一個用於接收用戶提交的數據,一個用於獲取數據庫的數據,接下來我們編寫Controller.
三、控制器(Controller)
控制器,主要是用於數據的提交,把用戶提交的數據填充到相應的模型(Model)中,然后根據模型返回的信息進一步渲染視圖(View),或者執行其他邏輯。
這里,把控制器命名為LoginController.php,以下是完整的實現代碼:
- <?php
- namespace app\controllers;
- use Yii;
- use yii\filters\AccessControl;
- use yii\web\Controller;
- use yii\filters\VerbFilter;
- use app\models\LoginForm;
- use app\models\ContactForm;
- class SiteController extends Controller
- {
- public function actionIndex()
- {
- return $this->render('index');
- }
- public function actionLogin()
- {
- if (!\Yii::$app->user->isGuest) { //①
- return $this->goHome();
- }
- $model = new LoginForm(); //②
- if ($model->load(Yii::$app->request->post()) && $model->login()) { //③
- return $this->goBack(); //④
- }
- return $this->render('login', [ //⑤
- 'model' => $model,
- ]);
- }
- public function actionLogout()
- {
- Yii::$app->user->logout();
- return $this->goHome();
- }
- }
關注其中的actionLogin()方法:
①首先從\Yii::$app->user->isGuest中判斷,當前是否是游客模式,即未登陸狀態,如果用戶已經登陸,會在user類中儲存當前登陸用戶的信息。
②如果當前是游客,會先實例化一個LoginForm模型
③這行 代碼是整個login方法的核心所在,首先:$model->load(Yii::$app->request->post())把 post過來的數據填充進$model,即LoginForm模型,如果返回true,則填充成功。接着:$model->login():執行 LoginForm類里面的login()方法,可以從login()方法里面看到,將會執行一系列的驗證。
關於Yii框架到底是怎樣進行用戶登陸的,底層是怎樣實現的,我們在下一篇文章詳談,這里先說明實現方法。
四、視圖(View)
在實現了model和controller,接下來是視圖部分,由於用戶需要輸入數據,所以我們要提供一個表單,在Yii2中,提供了ActiveForm快速生成表單,代碼如下:
- <?php
- /* @var $this yii\web\View */
- /* @var $form yii\bootstrap\ActiveForm */
- /* @var $model app\models\LoginForm */
- use yii\helpers\Html;
- use yii\bootstrap\ActiveForm;
- $this->title = 'Login';
- $this->params['breadcrumbs'][] = $this->title;
- ?>
- <div class="site-login">
- <h1><?= Html::encode($this->title) ?></h1>
- <p>Please fill out the following fields to login:</p>
- <?php $form = ActiveForm::begin([
- 'id' => 'login-form',
- 'options' => ['class' => 'form-horizontal'],
- 'fieldConfig' => [
- 'template' => "{label}\n<div class=\"col-lg-3\">{input}</div>\n<div class=\"col-lg-8\">{error}</div>",
- 'labelOptions' => ['class' => 'col-lg-1 control-label'],
- ],
- ]); ?>
- <?= $form->field($model, 'username')->textInput(['autofocus' => true]) ?>
- <?= $form->field($model, 'password')->passwordInput() ?>
- <?= $form->field($model, 'rememberMe')->checkbox([
- 'template' => "<div class=\"col-lg-offset-1 col-lg-3\">{input} {label}</div>\n<div class=\"col-lg-8\">{error}</div>",
- ]) ?>
- <div class="form-group">
- <div class="col-lg-offset-1 col-lg-11">
- <?= Html::submitButton('Login', ['class' => 'btn btn-primary', 'name' => 'login-button']) ?>
- </div>
- </div>
- <?php ActiveForm::end(); ?>
- <div class="col-lg-offset-1" style="color:#999;">
- You may login with <strong>admin/admin</strong> or <strong>demo/demo</strong>.<br>
- To modify the username/password, please check out the code <code>app\models\User::$users</code>.
- </div>
- </div>
$form=ActiveForm::begin() :創建一個Form表單
$form=field()->textInput() :創建一個文本輸入框
$form=field()->checkbox() :創建一個checkbox
Html::submitButton(): 創建一個登陸按鈕
ActiveForm::end() : 結束表單
以上,就是創建一個用戶登陸模塊的全流程,這里對用戶登陸的細節和怎樣實現cookie自動登陸只是一筆帶過,更詳細的源碼分析請看下一篇博文,謝謝。