Yii2 用戶認證體系

1. 用戶認證體系基本概念及實現;

相關概念:
認證: 鑑定用戶身份的過程。它通常使用一個標識符(如用戶名或電子郵件地址)和一個加密令牌(比如密碼或者存取令牌)來鑑別用戶身份
認證是登錄功能的基礎
認證框架:
相關組件:該框架連接了不用的組件以支持登錄。欲使用這個框架,主要需要做一下工作
設置用戶組件 yii\web\User;
要想使用組件,需要創建一個類實現 yii\web\IdentityInterface 接口,再連接 yii\web\User 組件,就可以實現登錄、退出過程的操作
用戶組件 yii\web\User
用來管理用戶的認證狀態(登錄、退出、獲取用戶信息)
需要制定一個含有實際認證邏輯的認證類繼承實現 yii\web\IdentityInterface 接口,實現以後再去連接 yii\web\User 組件,去完成認證服務
爲什麼有了 yii\web\User 組件,爲什麼還要去實現一個邏輯,再去連接組件?yii\web\User 組件只是提供了一個基本的框架,但是具體的實現邏輯需要按照具體的業務邏輯去實現,每個 Web 應用的認證邏輯都是不一樣的。所以需要去實現延展 yii\web\IdentityInterface 這個接口(規範)。實現之後再連接組件,才能提供服務。
實現後的實例作爲 yii\web\User 組件的身份驗證實例。
認證接口 yii\web\IdentityInterface 需要實現的方法
yii\web\IdentityInterface::findIdentity():根據用戶的 id 查找認證模型類的實例,當使用 session 來維持登錄狀態的時候,會用到這個方法
yii\web\IdentityInterface::findIdentityByAccessToken():根據 AccessToken 的值來獲取用戶認證實例。通常 AccessToken 和用戶是進行邦定的,這種一般運用在無狀態的 RESTful 這樣的應用下
yii\web\IdentityInterface::getId() :獲取用戶認證實例 id
yii\web\IdentityInterface::getAuthKey():獲取基於 cookie 登錄時的一個認證祕鑰
yii\web\IdentityInterface::ValidateAuthKey():認證 cookie 登錄祕鑰的內容
不一定所有的方法都要實現,不實現的方法可以留空
實例
實例頁面:http://192.168.2.214/yii22/basic/web/index.php?r=site/login
// 在 basic/config/web.php 中設置組件:
$config = [
	'components' => [
		// yii\web\User 組件
		'user' => [
			// 指定屬性 identityClass,連接 User 組件的實例是誰(app\models\User)
            'identityClass' => 'app\models\User',
            'enableAutoLogin' => true,	// 保持自動登錄
        ],
	],
];

// 找到 app\models\User
// 打開 basic/models/User.php
// 繼承 \yii\web\IdentityInterface
class User extends \yii\base\BaseObject implements \yii\web\IdentityInterface{}

// 打開 basic/controllers/SiteController.php
public function actionLogin() {
	// 判斷是否登錄
	// 用到了 User 組件相關屬性
    if (!Yii::$app->user->isGuest) {
        return $this->goHome();	// 登錄狀態,返回首頁
    }

    $model = new LoginForm();
    // post 數據載入
    if ($model->load(Yii::$app->request->post()) && $model->login()) {
        return $this->goBack();
    }

    $model->password = '';
    return $this->render('login', [
        'model' => $model,
    ]);
}

// 打開 basic/models/LoginForm.php
public function login(){
    if ($this->validate()) {
    	// 調用配置文件裏設置好的 user 組件裏的 login() 方法
    	// 這個 login() 方法就是 user 組件提供的 login() 方法,不是我們自己寫的
    	// login() 方法的第一個參數 $this->getUser() 是我們自己寫的,獲取用戶實例,
    	// 第二個參數保存登錄時間(配置文件設置 enableAutoLogin = true)
        return Yii::$app->user->login($this->getUser(), $this->rememberMe ? 3600*24*30 : 0);
    }
    return false;
}
總結用戶登錄流程
首先頁面點擊登錄的時候,提交給了 basic/controllers/SiteController.php 的 actionLogin()
第一步先 new LoginForm() 實例化,調用 basic/models/LoginForm.php 裏的 login() 方法,這個方法會去調用組件的 Yii::$app->user->login() 方法,把用戶實例和相關內容傳遞進去,就會自動的往 cookie 和 session 裏寫入用戶相關信息,保存了登錄狀態
basic/models/User.php 作用就是繼承 \yii\web\IdentityInterface 接口,實現方法
private static $users 指定了兩個用戶,
findIdentity($id) 方法獲取用戶實例,user 組件會自動調用這個方法
findIdentityByAccessToken($token, $type = null) 根據 Token 獲取用戶實例
findByUsername( $username ) 是自己寫的方法,根據用戶名將用戶實例返回。在 LoginForm() 裏面的 getUser() 方法,用到了 findByUsername( $username ) 方法獲取用戶實例,在 LoginForm() 裏面的 login() 方法中,傳遞給 user 組件裏的 login() 方法
getId() 獲取 model 實例 id
退出流程
頁面右上角點擊退出的時候,會調用 basic/controllers/SiteController.php 中的 actionLogout()
<?php
 public function actionLogout(){
 	// user 組件提供的 logout() 方法,系統會自動清空 cookie 和 session 數據
    Yii::$app->user->logout();

    return $this->goHome();
}

2. 用戶認證組件 User 相關屬性和方法完成前臺的登錄和退出;

用戶組件 yii\web\User 相關屬性:
identity:Yii::$app->user->identity;當前用戶的身份實例,未認證用戶則爲 NULL
id:Yii::$app->user->id;當前用戶的 id,未認證用戶則爲 NULL
isGuest:Yii::$app->user->isGuest;判斷當前用戶是否爲遊客(未認證)
相關方法:
login:將當前用戶的身份登記到 yii\web\User(第一個參數傳遞用戶實例,第二個參數爲保留的登錄時間)
‐ 登錄相關屬性 1:enableSession,是否啓用 session 來保存會話狀態,默認 true
‐ 相關屬性 2:enableAutoLogin,是否啓用自動登錄,默認 true,可以在配置文件中配置
logout:註銷用戶,啓用 session 時註銷用戶纔有意義。可以傳遞布爾值,傳遞 true 會把 session 信息全部清除,傳遞 false 會保留會話數據
// 以前的做法
<?php
// controller
// 登錄
public function actionAuth(){
	// 實例化 user
	$model = new User;
	if(Yii->$app->request->isPost){
		$post = Yii->$app->request->Post();
		// post 參數傳遞到 model 裏的 login()方法
		if($model->login($post)){
			%url = Yii::$app->session->getFlash('referrer');
			return $this;
		}
	}
	return $this->render("auth", ['model' => $model]) 
}

// 退出
public function actionLogout(){
	Yii->$app->session->remove('loginname');
	Yii->$app->session->remove('isLogin');
	
	if(!isset(Yii->$app->session['isLogin'])){
		return $this->goBack(Yii:$app->request->referer); 
	}
}

// model
public function login($data){
	$this->scenario = "login";
	if($this->load($data) && $this->validate()){
		// 設定保留時長
		$lifetime = $this->rememberMe ? 24*3600 : 0;
		// 實例化 session 實例
		$session = Yii::$app->session;
		// 設置 存儲 sessionId 的 cookie 的有效時間
		// 去找到對應的 session 文件
		// 下次打開瀏覽器的時候,會根據 cookie 存儲的 sessionId 找到對應的 seesion 文件
		session_set_cookie_params($lifetime);
		$session['loginname'] = $this->loginname;
		$session['isLogin'] = 1;
		return (bool)$session['isLogin'];
	}
	return false;
}
通過用戶組件去完成登錄操作:
在 basic/config/web.php 中設置(components),然後繼承 \yii\web\IdentityInterface 接口,實現 5 個方法
// model
<?php

namespace app\models;

use yii\db\ActiveRecord;
use Yii;

class User extends ActiveRecord implements \yii\web\IdentityInterface{

	public static function findIdentity($id){
		// 根據主鍵 id 獲取對應的用戶實例
		return static::findOne($id);
	}
	
 	public static function findIdentityByAccessToken($token, $type = null){
 		// 不是 RESTful 應用,可以不實現
 		// 如果要實現,可以在數據表中存一個字段(token),根據 token 來查詢對應的用戶的實例
 		return NULL;
 	}

	public function getId(){
		// 獲取用戶實例的表的主鍵 id
		return $this->id;
	}

	public function getAuthKey(){
		// 和 AccessToken 有點像,也是在數據表中存一個字段
		// 當使用 cookie 來做一個登錄的時候,要驗證 cookie,
		// 可以把對應的 AuthKey 存到數據表裏,
		// 然後要使用 cookie 的時候先要驗證一下
		// 如果是 session 就不需要使用這個方法
		return '';
	}

	public function validateAuthKey($authKey){
		// 將用戶傳遞進來的 AuthKey 做一個驗證,
		// 一致返回 true,不一致返回 false
		return true;
	}

	public function getUser(){
		// 返回用戶實例,調用 Yii::$app->user->login() 傳遞進去給 user 組件,
		// user 組件會連接這個 Model,調用繼承 \yii\web\IdentityInterface 接口實現的 5 個方法
		// 5 個方法什麼時候調用,你不用管,框架會自己調
		return self::find()->where('username = :loginname or useremail = :loginname', [
			':loginname' => $this->loginname
		])->one();
	} 

	public function login($data){
		$this->scenario = "login";
		if($this->load($data) && $this->validate()){
			return Yii::$app->user->login($this->getUser(), $this->rememberMe ? 24*3600 : 0);
		}
		return false;
	}

}

登錄成功後頁面顯示用戶名:
不用 session,使用 user 組件返回的一些內容進行操作
// layout
<?php
use yii\helpers\Html;
use yii\bootstrap\Nav;
use yii\bootstrap\NavBar;

?>

<?php
	NavBar::begin([
		'options' =>[
			'class' => 'top-bar animate-dropdown',
		],
	]);
	echo Nav::widget([
		'options' => ['class' => 'navbar-nav navbar-left'],
		'item' => [
			['label' => '首頁', 'url' => ['/site/index']],
			!\Yii::$app->user->isGuest ? (
				['label' => '購物車', 'url' => ['/cart/index']]
			) : '',
			!\Yii::$app->user->isGuest ? (
				['label' => '我的訂單', 'url' => ['/cart/index']]
			) : '',
           
		],
	]);
	echo Nav::widget([
		'options' => ['class' => 'navbar-nav navbar-right'],
		'item' => [
			\Yii::$app->user->isGuest ? (
				['label' => '註冊', 'url' => ['/member/auth']]
			) : '',
			\Yii::$app->user->isGuest ? (
				['label' => '登錄', 'url' => ['/member/login']]
			) : '',
			!\Yii::$app->user->isGuest ? (
				'歡迎您回來,' . \Yii::$app->user->identity->username . 
				Html::a('退出', ['member/logout'])
			) : '',      
		],
	]);
	
	NavBar::end();
?>
用戶的退出操作:
不用清除 session,使用 user 組件的 logout() 自動清除
// controller

public function actionLogout(){
	Yii->$app->user->logout();
	return $this->goBack(Yii:$app->request->referer); 
}

3. 過濾器 AccessControl 控制認證用戶;

// controller
// 未登錄跳轉到登錄界面
if(Yii->$app->user->isGuest){
	return $this->redirect(['member/auth']); 
}

// 獲取 id
$userid = Yii::$app->user->id;
$orders = Order::getProducts($userid);

// 但是,以上只是使用了認證體系,並沒有去改變每個方法裏都要去寫這麼一些判斷
// 解決方法:需要在控制器類中加入**過濾器**,幫助過濾當前用戶是否已經登錄
// 在 controller 里加入 behaviors() 方法
// Yii 會自動調用,在所有方法執行之前做相關驗證
// 注意:每一個控制器裏都要寫一個 behaviors()
public function behaviors(){
	// 做訪問控制
	return [
		'access' => [
			'class' => \yii\filters\AccessControl::className(),
			'only' => ['*'],	// 對控制器中的哪些方法做驗證,針對哪些方法有效
			'except' => [],
			'rules' => [
				[
					'allow' => false,
					'action' => ['index', 'check'],
					'roles' => ['?'], 	// guest,未登錄用戶不能訪問這兩個方法
				],
				[
					'allow' => true,
					'action' => ['index', 'check'],
					'roles' => ['@'],	// 登錄以後的用戶有權訪問 index 和 check
				]
			]
		]
	];
}

// 無權訪問跳到哪個頁面
// 在 basic/config/web.php 中設置 loginUrl
// 這樣在 controller 中連跳轉都不用設置
 'user' => [
   'identityClass' => 'app\models\User',
    'enableAutoLogin' => true,
    'loginUrl'=> ['/member/auth']
],
  • 也可以把 behaviors() 提取出來單獨放到一個父類裏面,所有 controller 繼承這個父類
// CommonController
<?php
namespace app\controllers;

use app\models\User;
use app\models\Product;
use Yii;

class CommonController extends Controller{

	protected $actions = ['*'];
	protected $except = [];
	protected $mustLogin = [];	// 必須做驗證的方法
	protected $verbs = [];

	public function behaviors(){
		return [
			'access' => [
				'class' => \yii\filters\AccessControl::className(),
				'only' => $this->actions,	// 對控制器中的哪些方法做驗證,針對哪些方法有效
				'except' => $this->except,
				'rules' => [
					[
						'allow' => false,
						'action' => empty($this->mustLogin) ? [] : $this->mustLogin,
						'roles' => ['?'], 	// guest,未登錄用戶不能訪問這兩個方法
					],
					[
						'allow' => true,
						'action' => empty($this->mustLogin) ? [] : $this->mustLogin,
						'roles' => ['@'],	// 登錄以後的用戶有權訪問 $this->mustLogin
					]
				]
			],
			'verbs' => [	// 過濾器
	            'class' => \yii\filters\VerbFilter::className(),
	            'actions' => $this->verbs,
	        ],
		];
	}
	
}

// 其它 Controller
class XxController extends CommonController{
	// 覆蓋基類的 $mustLogin
	protected $mustLogin = ['index', 'check', 'add', 'pay', 'received'];
	protected $verbs = [
		'confirm' => ['post'],
	];
}

4. 過濾器 VerbFilter 過濾請求方式;

VerbFilter 的作用
在一些方法當中,比如做了 POST 驗證 Yii::$app->request->isPost,方法只能是 POST 請求,如果是 GET 請求就報錯,返回一個異常
如果現在要用過濾器做,同樣可以通過 behavior() 操作
// 以前的方法
if(!Yii::$app->request->isPost){
	throw new \Exception();
}

// 過濾器
public function behaviors(){
    return [
        'access' => [
            'class' => \yii\filters\AccessControl::className(),
            'only' => ['logout'],
            'rules' => [
                [
                    'actions' => ['logout'],
                    'allow' => true,
                    'roles' => ['@'],
                ],
            ],
        ],
        // 訪問方式的過濾
        'verbs' => [
            'class' => \yii\filters\VerbFilter::className(),
            'actions' => [
                'logout' => ['post'],	// 只允許 post   
            ],
        ],
    ];
}

5. 分離前後臺用戶認證;

  • 待更新

6. 後臺使用過濾器驗證用戶;

  • 待更新

7. 哈希算法 bcrypt 對密碼加密處理。

相關概念
數據表存儲密碼,一般不會存取明文,而是通過某種哈希方式對密碼進行哈希化存儲。
哈希方式:MD5,SHA1
但是隨着現代硬件的發展,黑客可以在短時間內對 MD5 或者 SHA1 哈希化後的密碼進行暴力破解。所以需要更加安全的算法。這種算法叫 bcrypt
Yii2 框架提供了兩個方法幫助使用 bcrypt 對密碼進行哈希算法
bcrypt 相關方法
Yii->$app->getSecurity()->generatePasswordHash($password):可以將用戶輸入的密碼進行 bcrypt 哈希,然後再存儲到數據表中
Yii->$app->getSecurity()->validatePassword($password, $hash):對密碼驗證
// 原來的加密方式
$this->userpass = md5($this->userpass);

// bcrypt 哈希加密
// 數據表 userpass char(32) 修改爲 userpass char(64)
// 創建密碼
$this->userpass = Yii->$app->getSecurity()->generatePasswordHash($this->userpass);
// 驗證,第一個參數爲密碼字符串,第二個爲 hash 過的字符串,驗證成功返回true
$pass = Yii->$app->getSecurity()->validatePassword($this->userpass, $data->userpass);
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章