Yii2 認證明現原理和示例

  Yii的用戶認證分爲兩個部分,一個是User組件,負責管理用戶認證狀態的,包括登陸,登出,檢測當前登陸狀態等,源文件位於vender/yiisoft/yii2/web/User.php。另外一個是實現接口IdentityInterface的模型,同時必須繼承ActiveRecord,當用戶登陸註冊時,組件User會經過模型中的接口方法,對用戶進行驗證。php

  對於用戶狀態切換主要經過switchIdentity方法實現的,好比註冊後,用戶登陸時,會用到User組件中的switchIdentity方法,以下:ios

/**
     * Switches to a new identity for the current user.
     *
     * When [[enableSession]] is true, this method may use session and/or cookie to store the user identity information,
     * according to the value of `$duration`. Please refer to [[login()]] for more details.
     *
     * This method is mainly called by [[login()]], [[logout()]] and [[loginByCookie()]]
     * when the current user needs to be associated with the corresponding identity information.
     *
     * @param IdentityInterface|null $identity the identity information to be associated with the current user.
     * If null, it means switching the current user to be a guest.
     * @param integer $duration number of seconds that the user can remain in logged-in status.
     * This parameter is used only when `$identity` is not null.
     */
    public function switchIdentity($identity, $duration = 0)
    {
        $this->setIdentity($identity);

        if (!$this->enableSession) {
            return;
        }

        /* Ensure any existing identity cookies are removed. */
        if ($this->enableAutoLogin) {
            $this->removeIdentityCookie();
        }

        $session = Yii::$app->getSession();
        if (!YII_ENV_TEST) {
            $session->regenerateID(true);
        }
        $session->remove($this->idParam);
        $session->remove($this->authTimeoutParam);

        if ($identity) {
            $session->set($this->idParam, $identity->getId());
            if ($this->authTimeout !== null) {
                $session->set($this->authTimeoutParam, time() + $this->authTimeout);
            }
            if ($this->absoluteAuthTimeout !== null) {
                $session->set($this->absoluteAuthTimeoutParam, time() + $this->absoluteAuthTimeout);
            }
            if ($duration > 0 && $this->enableAutoLogin) {
                $this->sendIdentityCookie($identity, $duration);
            }
        }
    }

  若是寫入identity爲null,則將用戶狀態設置爲離線,若是不是null,而是模型實例,該方法會將用戶模型的id值寫入session中,用戶打開新頁面時,只需下面的方法就能夠判斷是否已經登陸。web

//已登陸返回true
Yii::$app->user->isGuest

  訪問user組件的isGuest屬性,會經過魔術方法,調用User組件中的getIsGuest方法bootstrap

    /**
     * Returns a value indicating whether the user is a guest (not authenticated).
     * @return boolean whether the current user is a guest.
     * @see getIdentity()
     */
    public function getIsGuest()
    {
        return $this->getIdentity() === null;
    }

  方法又調用getIdentity()方法yii2

    /**
     * Returns the identity object associated with the currently logged-in user.
     * When [[enableSession]] is true, this method may attempt to read the user's authentication data
     * stored in session and reconstruct the corresponding identity object, if it has not done so before.
     * @param boolean $autoRenew whether to automatically renew authentication status if it has not been done so before.
     * This is only useful when [[enableSession]] is true.
     * @return IdentityInterface|null the identity object associated with the currently logged-in user.
     * `null` is returned if the user is not logged in (not authenticated).
     * @see login()
     * @see logout()
     */
    public function getIdentity($autoRenew = true)
    {
        if ($this->_identity === false) {
            if ($this->enableSession && $autoRenew) {
                $this->_identity = null;
                $this->renewAuthStatus();
            } else {
                return null;
            }
        }

        return $this->_identity;
    }

  當session啓用時,經過renewAuthStatus()更新新用戶狀態cookie

    /**
     * Updates the authentication status using the information from session and cookie.
     *
     * This method will try to determine the user identity using the [[idParam]] session variable.
     *
     * If [[authTimeout]] is set, this method will refresh the timer.
     *
     * If the user identity cannot be determined by session, this method will try to [[loginByCookie()|login by cookie]]
     * if [[enableAutoLogin]] is true.
     */
    protected function renewAuthStatus()
    {
        $session = Yii::$app->getSession();
        $id = $session->getHasSessionId() || $session->getIsActive() ? $session->get($this->idParam) : null;

        if ($id === null) {
            $identity = null;
        } else {
            /* @var $class IdentityInterface */
            $class = $this->identityClass;
            $identity = $class::findIdentity($id);
        }

        $this->setIdentity($identity);

        if ($identity !== null && ($this->authTimeout !== null || $this->absoluteAuthTimeout !== null)) {
            $expire = $this->authTimeout !== null ? $session->get($this->authTimeoutParam) : null;
            $expireAbsolute = $this->absoluteAuthTimeout !== null ? $session->get($this->absoluteAuthTimeoutParam) : null;
            if ($expire !== null && $expire < time() || $expireAbsolute !== null && $expireAbsolute < time()) {
                $this->logout(false);
            } elseif ($this->authTimeout !== null) {
                $session->set($this->authTimeoutParam, time() + $this->authTimeout);
            }
        }

        if ($this->enableAutoLogin) {
            if ($this->getIsGuest()) {
                $this->loginByCookie();
            } elseif ($this->autoRenewCookie) {
                $this->renewIdentityCookie();
            }
        }
    }

  該方法主要經過session取出用戶id,而後經過id獲取用戶模型實例,而後使用實例進行登陸,和更新認證過時時間。session

  下面實現一個經常使用的用戶註冊登陸功能模塊,用戶只有登陸後才能夠進入home頁面app

  User模型:dom

<?php
/**
 * Created by PhpStorm.
 * User: zhenbao
 * Date: 16/10/17
 * Time: 下午4:14
 */

namespace app\models;

use Yii;
use yii\web\IdentityInterface;
use yii\db\ActiveRecord;

class User extends ActiveRecord implements IdentityInterface
{
    const LOGIN = "login";
    const REGISTER = "register";

    public function scenarios()
    {
        return [
            self::LOGIN => ['username', 'password'],
            self::REGISTER => ['username', 'password']
        ];
    }

    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 function getId()
    {
        return $this -> id;
    }

    public function getAuthKey()
    {
        return $this -> authKey;
    }

    public function validateAuthKey($authKey)
    {
        return $this -> getAuthKey() === $authKey;
    }

    public static function findIdentityByUsername($username)
    {
        return static::findOne(['username' => $username]);
    }

    public function validatePassword($password)
    {
        return $this -> password === sha1($password);
    }

    public function setPassword()
    {
        $this -> password = sha1($this -> password);
        return true;
    }

    public function beforeSave($insert)
    {
        if(parent::beforeSave($insert))
        {
            if($this -> isNewRecord)
            {
                $this -> authKey = Yii::$app -> security -> generateRandomString();
            }
            return true;
        }
        return false;
    }
}

  控制器:yii

<?php

namespace app\controllers;

use Yii;
use yii\web\Controller;
use app\models\User;
use yii\web\Cookie;

class UserController extends Controller
{
    /**默認方法爲home方法
     * @var string
     */
    public $defaultAction = 'home';

    /**用戶登陸,當已經登陸直接跳轉home頁面,
     * 不然,查看是否有訪問accessToken,若是有使用accessToken登陸,若是accessToken無效則刪除accessToken而後返回到登陸界面
     * 若是沒有accessToken則使用用戶的登陸密碼登陸,驗證成功後,查看是否選擇了記住我,有則生成accessToken,下次直接使用accessToken登陸
     * 登陸失敗,返回到登陸界面從新登陸。
     * @return string|\yii\web\Response
     */
    public function actionLogin()
    {
        $request = Yii::$app->request->post('User');
        //若是已經登陸獲取認證identity而後進入home頁面
        if (!(Yii::$app->user->isGuest)) {
            return $this->redirect('/?r=user/home');
        } else {
            //若是沒有登陸,查看cookie中是否有accessToken,若是有嘗試使用accessToken登陸,accessToken登陸失敗,則刪除這個無效的accessToken
            $accessToken = Yii::$app->request->cookies->getValue('accessToken');
            if ($accessToken !== null) {
                if (Yii::$app->user->loginByAccessToken($accessToken)) {
                    return $this->redirect("/?r=user/home");
                } else {
                    Yii::$app->request->cookies->remove("accessToken");
                    $user = new User(['scenario' => 'login']);
                    return $this->renderPartial('login', ['model' => $user]);
                }
            }
            //嘗試用戶名密碼登陸,若是驗證成功,查看是否有點擊記住我,若是有生成accessToken,下次直接accessToken登陸
            $request = Yii::$app->request->post('User');
            if ($request && isset($request['username']) && isset($request['password'])) {
                $user = User::findIdentityByUsername($request['username']);
                if ($user && $user->validatePassword($request['password'])) {
                    $remeberMe = Yii::$app->request->post('remeberMe');
                    if ($remeberMe === 'on') {
                        //生成訪問accessToken
                        $user->accessToken = Yii::$app->security->generateRandomString();
                        $user->scenario = 'login';
                        $user->save();
                        Yii::$app->response->cookies->add(new Cookie([
                            'name' => 'accessToken',
                            'value' => $user->accessToken,
                            'expire' => time() + 3600 * 24 * 7
                        ]));
                    }
                    Yii::$app->user->login($user);
                    return $this->redirect('/?r=user/home');
                }
            }
            //accessToken和帳號密碼均沒法登陸,從新返回登陸界面
            $user = new User(['scenario' => 'login']);
            return $this->renderPartial('login', ['model' => $user]);

        }
    }

    /**根據用戶是否登陸,選擇跳轉頁面
     * @return string|\yii\web\Response
     */
    public function actionHome()
    {
        if (Yii::$app->user->isGuest) {
            return $this->redirect('/?r=user/login');
        }
        $user = Yii::$app->user->getIdentity();
        return $this->renderPartial('home', ['user' => $user]);
    }

    /**退出登陸,若是有刪除accessToken,返回登陸頁面
     * @return \yii\web\Response
     */
    public function actionLogout()
    {
        $user = Yii::$app->user->getIdentity();
        Yii::$app->user->logout($user);
        $accessToken = Yii::$app->request->cookies->getValue('accessToken');
        if ($accessToken !== null) {
            Yii::$app->response->cookies->remove('accessToken');
        }
        return $this->redirect('/?r=user/login');
    }

    /**
     * 註冊用戶,若是註冊成功自動進入home主頁,註冊失敗進入註冊頁面
     * @return string|\yii\web\Response
     */
    public function actionRegister()
    {
        $user = new User(['scenario' => 'register']);
        $request = Yii::$app->request->post();
        if ($request) {
            if ($user->load($request) && $user->setPassword() && $user->save()) {
                Yii::$app->user->login($user);
                return $this->redirect('/?r=user/login');
            }
        }
        return $this->renderPartial('register', ['model' => $user]);
    }
}

  視圖login

<?php

use yii\helpers\Html;
use yii\bootstrap\ActiveForm;

 $form = ActiveForm::begin([
        'id' => 'user',
        '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() ?>
<input type="checkbox" name="remeberMe" >記住我
<?= Html::submitButton('Login', ['class' => 'btn btn-primary', 'name' => 'login-button']) ?>
<?php ActiveForm::end(); ?>
<a href="/?r=user/register">註冊</a>

  視圖註冊:

<?php

use yii\helpers\Html;
use yii\bootstrap\ActiveForm;

$this->title = 'register';

$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() ?>
<?= Html::submitButton('Register', ['class' => 'btn btn-primary', 'name' => 'login-button']) ?>
<?php ActiveForm::end(); ?>
<a href="/?r=user/login">登陸</a>

  home視圖:

<?php
    echo $user -> username;
?>
<a href="/?r=user/logout">退出</a>

  運行:

home頁面

 

文檔:http://www.yiichina.com/doc/guide/2.0/security-authentication

相關文章
相關標籤/搜索