Yii的用戶認證分為兩個部分,一個是User組件,負責管理用戶認證狀態的,包括登錄,登出,檢測當前登錄狀態等,源文件位於vender/yiisoft/yii2/web/User.php。另一個是實現接口IdentityInterface的模型,同時必須繼承ActiveRecord,當用戶登錄注冊時,組件User會通過模型中的接口方法,對用戶進行驗證。
對於用戶狀態切換主要通過switchIdentity方法實現的,比如注冊后,用戶登錄時,會用到User組件中的switchIdentity方法,如下:
/**
* 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中,用戶打開新頁面時,只需下面的方法就可以判斷是否已經登錄。
//已登錄返回true Yii::$app->user->isGuest
訪問user組件的isGuest屬性,會通過魔術方法,調用User組件中的getIsGuest方法
/**
* 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()方法
/**
* 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()更新新用戶狀態
/**
* 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獲取用戶模型實例,然后使用實例進行登錄,和更新認證過期時間。
下面實現一個常用的用戶注冊登錄功能模塊,用戶只有登錄后才可以進入home頁面
User模型:
<?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;
}
}
控制器:
<?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
