在Yii2的basic版本中默認是從一個數組驗證用戶名和密碼,如何改為從數據表中查詢驗證呢?且數據庫的密碼要為哈希加密密碼驗證?
下面我們就一步一步解析Yii2的登錄過程。
表結構如下:
CREATE TABLE `user` ( `id` int(11) NOT NULL AUTO_INCREMENT, `pid` int(11) NOT NULL DEFAULT '0' COMMENT '父id', `username` char(70) NOT NULL COMMENT '用戶名', `password` char(70) NOT NULL COMMENT '密碼', `type` tinyint(4) NOT NULL DEFAULT '4' COMMENT '類型(1:總店,2:門店,3:管理員)', `created_time` int(11) NOT NULL COMMENT '注冊時間', `updated_time` int(11) NOT NULL COMMENT '修改時間', `status` tinyint(4) NOT NULL DEFAULT '1' COMMENT '封禁狀態,0禁止1正常', `login_ip` char(20) NOT NULL COMMENT '登錄ip', `login_time` int(11) NOT NULL COMMENT '上一次登錄時間', `login_count` int(10) NOT NULL DEFAULT '0' COMMENT '登陸次數', `update_password` int(10) NOT NULL DEFAULT '0' COMMENT '修改密碼次數', PRIMARY KEY (`id`), KEY `pid` (`pid`), KEY `username` (`username`), KEY `type` (`type`), KEY `status` (`status`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='登錄管理表';
使用Gii創建user模型

將Yii2 basic之前user模型代碼導入現在user中(先備份之前basic中的user模型)
1 namespace app\models;
2
3 use Yii;
4
5 /**
6 * This is the model class for table "user".
7 *
8 * @property integer $id
9 * @property integer $pid
10 * @property string $username
11 * @property string $password
12 * @property integer $type
13 * @property integer $created_time
14 * @property integer $updated_time
15 * @property integer $status
16 * @property string $login_ip
17 * @property integer $login_time
18 * @property integer $login_count
19 * @property integer $update_password
20 */
21 class User extends \yii\db\ActiveRecord implements \yii\web\IdentityInterface
22 { public $authKey;
23 /*public $id;
24 public $username;
25 public $password;
26 public $authKey;
27 public $accessToken;
28
29 private static $users = [
30 '100' => [
31 'id' => '100',
32 'username' => 'admin',
33 'password' => 'admin',
34 'authKey' => 'test100key',
35 'accessToken' => '100-token',
36 ],
37 '101' => [
38 'id' => '101',
39 'username' => 'demo',
40 'password' => 'demo',
41 'authKey' => 'test101key',
42 'accessToken' => '101-token',
43 ],
44 ];
45 */
46
47 /**
48 * @inheritdoc
49 */
50 public static function tableName()
51 {
52 return 'user';
53 }
54
55 /**
56 * @inheritdoc
57 */
58 public function rules()
59 {
60 return [
61 [['pid', 'type', 'created_time', 'updated_time', 'status', 'login_time', 'login_count', 'update_password'], 'integer'],
62 [['username', 'password', 'created_time', 'updated_time', 'login_ip', 'login_time'], 'required'],
63 [['username', 'password'], 'string', 'max' => 70],
64 [['login_ip'], 'string', 'max' => 20]
65 ];
66 }
67
68 /**
69 * @inheritdoc
70 */
71 public function attributeLabels()
72 {
73 return [
74 'id' => 'ID',
75 'pid' => 'Pid',
76 'username' => 'Username',
77 'password' => 'Password',
78 'type' => 'Type',
79 'created_time' => 'Created Time',
80 'updated_time' => 'Updated Time',
81 'status' => 'Status',
82 'login_ip' => 'Login Ip',
83 'login_time' => 'Login Time',
84 'login_count' => 'Login Count',
85 'update_password' => 'Update Password',
86 ];
87 }
88
89 /**
90 * @inheritdoc
91 */
92 public static function findIdentity($id)
93 {
94 return static::findOne($id);
95 //return isset(self::$users[$id]) ? new static(self::$users[$id]) : null;
96 }
97
98 /**
99 * @inheritdoc
100 */
101 public static function findIdentityByAccessToken($token, $type = null)
102 {
103 return static::findOne(['access_token' => $token]);
104 /*foreach (self::$users as $user) {
105 if ($user['accessToken'] === $token) {
106 return new static($user);
107 }
108 }
109
110 return null;*/
111 }
112
113 /**
114 * Finds user by username
115 *
116 * @param string $username
117 * @return static|null
118 */
119 public static function findByUsername($username)
120 {
121 $user = User::find()
122 ->where(['username' => $username])
123 ->asArray()
124 ->one();
125
126 if($user){
127 return new static($user);
128 }
129
130 return null;
131 /*foreach (self::$users as $user) {
132 if (strcasecmp($user['username'], $username) === 0) {
133 return new static($user);
134 }
135 }
136
137 return null;*/
138 }
139
140 /**
141 * @inheritdoc
142 */
143 public function getId()
144 {
145 return $this->id;
146 }
147
148 /**
149 * @inheritdoc
150 */
151 public function getAuthKey()
152 {
153 return $this->authKey;
154 }
155
156 /**
157 * @inheritdoc
158 */
159 public function validateAuthKey($authKey)
160 {
161 return $this->authKey === $authKey;
162 }
163
164 /**
165 * Validates password
166 *
167 * @param string $password password to validate
168 * @return boolean if password provided is valid for current user
169 */
170 public function validatePassword($password)
171 {
172 return $this->password === $password;
173 }
174 }
之前的basic中User模型是繼承了\yii\base\Object,為什麼要繼承這個類,那是因為
1 #在\yii\base\Object中,有構造方法
2 public function __construct($config = [])
3 {
4 if (!empty($config)) {
5 Yii::configure($this, $config);
6 }
7 $this->init();
8 }
9 #繼續追蹤Yii::configure($this, $config)代碼如下
10 public static function configure($object, $properties)
11 {
12 foreach ($properties as $name => $value) {
13 $object->$name = $value;
14 }
15
16 return $object;
17 }
18 #正是因為有這兩個方法,所以在User.php中
19 public static function findByUsername($username)
20 {
21 foreach (self::$users as $user) {
22 if (strcasecmp($user['username'], $username) === 0) {
23 return new static($user);
24 }
25 }
26
27 return null;
28 }
29 #將$user傳遞過來,通過static,返回一個User的實例。
當通過數據表查詢時候沒有必要再繼承\yii\base\Object,因為不必為類似原來類變量賦值了。這個時候需要User模型繼承\yii\db\ActiveRecord,因為要查詢用。
findIdentity是根據傳遞的id返回對應的用戶信息,getId返回用戶id,getAuthKey和validateAuthKey是作用於登陸中的--記住我。這個authKey是唯一的,當再次登陸時,從cookie中獲取authKey傳遞給validateAuthKey,驗證通過,就登陸成功。
插入一條用戶模擬數據
INSERT INTO `user` (`username`, `password`) VALUES ('admin', '123')

控制器Controller
1 /**
2 * 登錄
3 */
4 public function actionLogin() {
5 if (!\Yii::$app->user->isGuest) {
6 return $this->goHome();
7 }
8
9 $model = new LoginForm();
10 if ($model->load(Yii::$app->request->post()) && $model->login()) {
11
12
13 $this->redirect(array('charisma/index'));
14 } else {
15 return $this->render('login', [
16 'model' => $model,
17 ]);
18 }
19 }
veiws中的login.php
1 <div class="well col-md-5 center login-box">
2 <div class="alert alert-info">
3 請填寫您的用戶名和密碼
4 </div>
5
6 <?php $form = ActiveForm::begin([
7 'id' => 'login-form',
8 ]); ?>
9
10 <fieldset>
11 <div class="input-group input-group-lg">
12 <span class="input-group-addon"><i class="glyphicon glyphicon-user red"></i></span>
13 <?php echo Html::input('type','LoginForm[username]', $model->username, ['class'=>'form-control','placeholder'=>'Username']); ?>
14 </div>
15 <div class="clearfix"></div><br>
16 <div class="input-group input-group-lg">
17 <span class="input-group-addon"><i class="glyphicon glyphicon-lock red"></i></span>
18 <?php echo Html::input('password','LoginForm[password]', $model->password, ['class'=>'form-control','placeholder'=>'Password']); ?>
19 </div>
20 <div class="clearfix"></div>
21
22 <div class="clearfix"></div>
23 <p class="center col-md-5">
24 <input type="submit" class="btn btn-primary" value="Login">
25 </p>
26 </fieldset>
27 <?php ActiveForm::end();?>
28
29 <?php
30 if($model->errors){
31 echo '用戶名或密碼錯誤';
32 print_r($model->errors);
33 }
34
35
36
37 ?>
38
39 </div>

用戶名admin, 密碼123, 登錄ok!
問題來了,我們使用的是明文保存的密碼,這樣很不安全,所以我們必須要把用戶注冊時的密碼哈希加密後再保存到數據庫。
YII2對密碼加密生成的結果是不同的,即用相同的初始密碼在不同時間得到的加密結果不同,所以我們不能用常用的方法去驗證密碼是否正確(將密碼加密後與數據庫中的密碼相比較)。YII2有自己的加密以及密碼驗證流程。
加密
$hash = Yii::$app->getSecurity()->generatePasswordHash('123456');
驗證
Yii::$app->getSecurity()->validatePassword('123456', $hash) ; #,返回true或false
我們先通過Yii的加密機制加密 “123” 獲取哈希密碼為: $2y$13$eXQl9YCo5XcKqqy9ymd2t.SuOvpXYERidceXoT/bPt4iwmOW3GiBy
修改模擬數據admin的密碼:
UPDATE `user` SET `password`='$2y$13$eXQl9YCo5XcKqqy9ymd2t.SuOvpXYERidceXoT/bPt4iwmOW3GiBy' WHERE (`username`='admin')
在控制器中我們通過 實例化LoginForm,
Yii::$app->request->post()來獲取post提交的值,通過$model->load()加載post數據
然後$model->login() 就是驗證登錄
$model = new LoginForm();
if ($model->load(Yii::$app->request->post()) && $model->login()) {
$this->redirect(array('charisma/index'));
}
我們跳轉到app\models\LoginForm的login方法
public function login()
{
if ($this->validate()) {
return Yii::$app->user->login($this->getUser(), $this->rememberMe ? 3600*24*30 : 0);
} else {
return false;
}
}
login方法又是通過一個validate驗證方法 繼承vendor/yiisoft/yii2/base/Model.php
該驗證方法描述是這樣的:Performs the data validation. This method executes the validation rules applicable to the current [[scenario]]. The following criteria are used to determine whether a rule is currently applicable: - the rule must be associated with the attributes relevant to the current scenario; - the rules must be effective for the current scenario. This method will call [[beforeValidate()]] and [[afterValidate()]] before and after the actual validation, respectively. If [[beforeValidate()]] returns false, the validation will be cancelled and [[afterValidate()]] will not be called. Errors found during the validation can be retrieved via [[getErrors()]], [[getFirstErrors()]] and [[getFirstError()]].
我們打開model類的validate()
public function validate($attributeNames = null, $clearErrors = true)
{
if ($clearErrors) {
$this->clearErrors();
}
if (!$this->beforeValidate()) {
return false;
}
$scenarios = $this->scenarios();
$scenario = $this->getScenario();
if (!isset($scenarios[$scenario])) {
throw new InvalidParamException("Unknown scenario: $scenario");
}
if ($attributeNames === null) {
$attributeNames = $this->activeAttributes();
}
foreach ($this->getActiveValidators() as $validator) {
$validator->validateAttributes($this, $attributeNames);
}
$this->afterValidate();
return !$this->hasErrors();
}
也就是說獲取到場景且沒有錯誤的話,將場景yii\validators\RequiredValidator Object的每一個屬性實例化為對應Form規則(rules)實例
foreach ($this->getActiveValidators() as $validator) {
$validator->validateAttributes($this, $attributeNames);
}
現在找到LoginForm的驗證規則
/**
* @return array the validation rules.
*/
public function rules()
{
return [
// username and password are both required
[['username', 'password'], 'required'],
// rememberMe must be a boolean value
['rememberMe', 'boolean'],
// password is validated by validatePassword()
['password', 'validatePassword'],
];
}
其中username,password 必填, rememberMe為波爾類型, password通過ValidatePassword方法來驗證
查看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.');
}
}
}
首先$this->getUser()會判斷在user表中是否有username=“admin”,如果存在就返回一個user的實例
public static function findByUsername($username) {
$user = User::find()
->where(['username' => $username])
->asArray()
->one();
if ($user) {
return new static($user);
}
return null;
}
通過$user->validatePassword($this->password) 判斷驗證密碼,這個是最關鍵的一步
因為之前通過$this->getUser 已經實例化了user表,所以validatePassword在User模型的原始代碼是這樣的
/**
* Validates password
*
* @param string $password password to validate
* @return boolean if password provided is valid for current user
*/
public function validatePassword($password) {
return $this->password === $password;
}
獲取用戶輸入的密碼, 再和數據庫中的密碼做對比,如果密碼相同就通過驗證。
現在我們已經把密碼123改為哈希密碼:$2y$13$eXQl9YCo5XcKqqy9ymd2t.SuOvpXYERidceXoT/bPt4iwmOW3GiBy
所以要通過Yii2 自帶的驗證 Yii::$app->getSecurity()->validatePassword('123456', $hash) ; 進行驗證
所以validatePassword方法的代碼應該修改如下:
/**
* Validates password
*
* @param string $password password to validate
* @return boolean if password provided is valid for current user
*/
public function validatePassword($password) {
return Yii::$app->getSecurity()->validatePassword($password, $this->password);
}
完整的LoginForm模型和User模型代碼如下:
1 <?php
2
3 namespace app\models;
4
5 use Yii;
6 use yii\base\Model;
7
8 /**
9 * LoginForm is the model behind the login form.
10 */
11 class LoginForm extends Model
12 {
13 public $username;
14 public $password;
15 public $rememberMe = true;
16
17 private $_user = false;
18
19
20 /**
21 * @return array the validation rules.
22 */
23 public function rules()
24 {
25 return [
26 // username and password are both required
27 [['username', 'password'], 'required'],
28 // rememberMe must be a boolean value
29 ['rememberMe', 'boolean'],
30 // password is validated by validatePassword()
31 ['password', 'validatePassword'],
32 ];
33 }
34
35 /**
36 * Validates the password.
37 * This method serves as the inline validation for password.
38 *
39 * @param string $attribute the attribute currently being validated
40 * @param array $params the additional name-value pairs given in the rule
41 */
42 public function validatePassword($attribute, $params)
43 {
44 if (!$this->hasErrors()) {
45 $user = $this->getUser();
46
47 if (!$user || !$user->validatePassword($this->password)) {
48 $this->addError($attribute, 'Incorrect username or password.');
49 }
50 }
51 }
52
53 /**
54 * Logs in a user using the provided username and password.
55 * @return boolean whether the user is logged in successfully
56 */
57 public function login()
58 {
59 if ($this->validate()) {
60 return Yii::$app->user->login($this->getUser(), $this->rememberMe ? 3600*24*30 : 0);
61 } else {
62 return false;
63 }
64 }
65
66 /**
67 * Finds user by [[username]]
68 *
69 * @return User|null
70 */
71 public function getUser()
72 {
73 if ($this->_user === false) {
74 $this->_user = User::findByUsername($this->username);
75 }
76
77 return $this->_user;
78 }
79 }
<?php
namespace app\models;
use Yii;
/**
* This is the model class for table "user".
*
* @property integer $id
* @property integer $pid
* @property string $username
* @property string $password
* @property integer $type
* @property integer $created_time
* @property integer $updated_time
* @property integer $status
* @property string $login_ip
* @property integer $login_time
* @property integer $login_count
* @property integer $update_password
*/
class User extends \yii\db\ActiveRecord implements \yii\web\IdentityInterface
{
public $authKey;
/**
* @inheritdoc
*/
public static function tableName()
{
return 'user';
}
/**
* @inheritdoc
*/
public function rules()
{
return [
[['pid', 'type', 'created_time', 'updated_time', 'status', 'login_time', 'login_count', 'update_password'], 'integer'],
[['username', 'password', 'created_time', 'updated_time', 'login_ip', 'login_time'], 'required'],
[['username', 'password'], 'string', 'max' => 70],
[['login_ip'], 'string', 'max' => 20]
];
}
/**
* @inheritdoc
*/
public function attributeLabels()
{
return [
'id' => 'ID',
'pid' => 'Pid',
'username' => 'Username',
'password' => 'Password',
'type' => 'Type',
'created_time' => 'Created Time',
'updated_time' => 'Updated Time',
'status' => 'Status',
'login_ip' => 'Login Ip',
'login_time' => 'Login Time',
'login_count' => 'Login Count',
'update_password' => 'Update Password',
];
}
/**
* @inheritdoc
*/
public static function findIdentity($id) {
return static::findOne($id);
}
/**
* @inheritdoc
*/
public static function findIdentityByAccessToken($token, $type = null) {
return null;
}
/**
* Finds user by username
*
* @param string $username
* @return static|null
*/
public static function findByUsername($username) {
$user = User::find()
->where(['username' => $username])
->asArray()
->one();
if ($user) {
return new static($user);
}
return null;
}
/**
* @inheritdoc
*/
public function getId() {
return $this->id;
}
/**
* @inheritdoc
*/
public function getAuthKey() {
return $this->authKey;
}
/**
* @inheritdoc
*/
public function validateAuthKey($authKey) {
return $this->authKey === $authKey;
}
/**
* Validates password
*
* @param string $password password to validate
* @return boolean if password provided is valid for current user
*/
public function validatePassword($password) {
return Yii::$app->getSecurity()->validatePassword($password, $this->password); #,返回true或false
}
#-------------------------------------輔助驗證-----------------------------------------------------------
public function createhashpasswd(){
echo Yii::$app->getSecurity()->generatePasswordHash('123');
}
}
ok,這樣就實現了哈希加密的用戶登錄了。