yii验证系统学习记录,基于yiicms(一)写的太长了,再写一篇(二)
項目地址:https://gitee.com/templi/yiicms?感謝七觴酒大神的付出,和免費分享。當然也感謝yii2的開發團隊們。
項目已經安全完畢,不知道后臺密碼,這種背景下,后臺無法進去。繞不開的話題就是:
1.后臺密碼賬號如何產生。類似下圖:
加密過的字符串自然不能用于登陸。
我嘗試通過前臺注冊一個賬戶,然后登陸,因為權限不夠,盡管等登陸到后臺首頁,同樣無權訪問其他任何頁面。配置了下modules,
意圖通過給予權限系統權限,然后更改剛加入的用戶“zhuangli”以管理員角色以及權限分配等。不成功。
我們先來梳理第一個問題,yii2的密碼到底是如何產生的?
我們從登陸這里開始,看它一步步都做了什么操作。
只要用戶訪問,必須經過yii2入口文件index.php,也就是在這個時候新創建了一個application實例,使用了配置文件中的各種配置項以及系統默認給定的幾個包括響應組件,請求組件,錯誤組件等核心組件的實例,附著在該應用上,方便隨時供訪問。其中這里配置了一個user,當然不配置使用系統默認也是可以的。
'user' => ['identityClass' => 'common\models\User','enableAutoLogin' => true,'identityCookie' => ['name' => '_identity-backend', 'httpOnly' => true],],這個組件規定了權限類,是否自動登陸以及權限驗證cookie
1 User is the class for the `user` application component that manages the user authentication status. 2 * 3 * You may use [[isGuest]] to determine whether the current user is a guest or not. 4 * If the user is a guest, the [[identity]] property would return `null`. Otherwise, it would 5 * be an instance of [[IdentityInterface]]. 6 * 7 * You may call various methods to change the user authentication status: 8 * 9 * - [[login()]]: sets the specified identity and remembers the authentication status in session and cookie; 10 * - [[logout()]]: marks the user as a guest and clears the relevant information from session and cookie; 11 * - [[setIdentity()]]: changes the user identity without touching session or cookie 12 * (this is best used in stateless RESTful API implementation). 13 * 14 * Note that User only maintains the user authentication status. It does NOT handle how to authenticate 15 * a user. The logic of how to authenticate a user should be done in the class implementing [[IdentityInterface]]. 16 * You are also required to set [[identityClass]] with the name of this class. 17 * 18 * User is configured as an application component in [[\yii\web\Application]] by default. 19 * You can access that instance via `Yii::$app->user`. 20 * 21 * You can modify its configuration by adding an array to your application config under `components` 22 * as it is shown in the following example: 23 * 24 * ```php 25 * 'user' => [ 26 * 'identityClass' => 'app\models\User', // User must implement the IdentityInterface 27 * 'enableAutoLogin' => true, 28 * // 'loginUrl' => ['user/login'], 29 * // ... 30 * ] 31 * ``` 32 * 33 * @property string|int $id The unique identifier for the user. If `null`, it means the user is a guest. This 34 * property is read-only. 35 * @property IdentityInterface|null $identity The identity object associated with the currently logged-in 36 * user. `null` is returned if the user is not logged in (not authenticated). 37 * @property bool $isGuest Whether the current user is a guest. This property is read-only. 38 * @property string $returnUrl The URL that the user should be redirected to after login. Note that the type 39 * of this property differs in getter and setter. See [[getReturnUrl()]] and [[setReturnUrl()]] for details. 40 *翻譯一下這段對user這個組件的說明:
這是一個user應用組件的類,管理用戶的認證狀態。可以使用isGuest(實際是getIsGuest)來辨別當前用戶是否用游客。如果為游客,identity屬性返回null,否則就是IdentityInterface接口的實例。有很多方法可以改變認證狀態。
login,設置特定的身份,并將認證狀態存在session或cookie中。
loginout,標示為游客,并將有關的信息從session或cookie中清除。
setIndentiy,只改變用戶身份,不涉及session和cookie。這最好用于無狀態的REST API實現。
?User 僅僅用于維護用戶認證狀態,它并不負責如何去認證一個用戶。如何去認證一個用戶的邏輯應在繼承至IdentityInterface類中去完成,User這個類也被要求設置為identityClass屬性的值。正如這里做的:
它是被默認配置為yii\web\application的一個應用組件,正如我上面所說。可以直接通過Yii::$app->user訪問到該組件。當然可以改變配置,通過在配置文件中的components底下加入數組,類似:
'user' => ['identityClass' => 'app\models\User', // User must implement the IdentityInterface
'enableAutoLogin' => true,
'loginUrl' => ['user/login'],
// ...
] $id唯一身份標示,為null,代表是游客,只讀。$identity關聯到當前登入用戶的認證對象,null代表未登入(未認證)。$isGuest,當前用戶是否游客,只讀。$returnUrl,登陸后重定向的url。
繼續看登陸的過程。
1 /**2 * Login action.3 *4 * @return string5 */6 public function actionLogin()7 {8 $this->layout = false;9 var_dump(Yii::$app->user); 10 11 if (!Yii::$app->user->isGuest) { 12 return $this->goHome(); 13 } 14 15 $model = new LoginForm(); 16 if ($model->load(Yii::$app->request->post()) && $model->login()) { 17 return $this->goBack(); 18 } else { 19 return $this->render('login', [ 20 'model' => $model, 21 ]); 22 } 23 }
首先是判斷用戶是否游客,不是,重定向到主頁去,新建一個loginForm模型實例,這是個登陸表單的虛擬數據模型,將username和password加載到這個model中,驗證執行登陸login操作。
1 /** 2 * Logs in a user using the provided username and password. 3 * 4 * @return bool whether the user is logged in successfully 5 */ 6 public function login() 7 { 8 if ($this->validate()) { 9 return Yii::$app->user->login($this->getUser(), $this->rememberMe ? 3600 * 24 * 30 : 0); 10 } else { 11 return false; 12 } 13 }開步就是驗證,這個validate方法是該類的父類model上的方法。
validate點進去是以下這樣一個方法:?1 /**?2 * Performs the data validation.
3 * 4 * This method executes the validation rules applicable to the current [[scenario]]. 5 * The following criteria are used to determine whether a rule is currently applicable: 6 * 7 * - the rule must be associated with the attributes relevant to the current scenario; 8 * - the rules must be effective for the current scenario. 9 * 10 * This method will call [[beforeValidate()]] and [[afterValidate()]] before and 11 * after the actual validation, respectively. If [[beforeValidate()]] returns false, 12 * the validation will be cancelled and [[afterValidate()]] will not be called. 13 * 14 * Errors found during the validation can be retrieved via [[getErrors()]], 15 * [[getFirstErrors()]] and [[getFirstError()]]. 16 * 17 * @param array $attributeNames list of attribute names that should be validated. 18 * If this parameter is empty, it means any attribute listed in the applicable 19 * validation rules should be validated. 20 * @param bool $clearErrors whether to call [[clearErrors()]] before performing validation 21 * @return bool whether the validation is successful without any error. 22 * @throws InvalidParamException if the current scenario is unknown. 23 */ 24 public function validate($attributeNames = null, $clearErrors = true) 25 {//清除錯誤 26 if ($clearErrors) { 27 $this->clearErrors(); 28 } 29
//前置驗證 30 if (!$this->beforeValidate()) { 31 return false; 32 } 33
//取回場景列表,該方法返回一個場景列表和活躍屬性,活躍屬性是指當前場景中需要驗證的。格式: ```php
* [
* 'scenario1' => ['attribute11', 'attribute12', ...],
* 'scenario2' => ['attribute21', 'attribute22', ...],
* ...
* ]
默認情況下,活躍屬性被認為安全,可以批量指定。如果不在批量指定之列,則認為不安全,這樣請加一個!作為前綴。這個方法的默認實現會返回所有在rules中能找到的場景聲明,一個特別的場景
是SCENARIO_DEFAULT,包括rules中的所有場景。每個場景將都通過運用到場景的驗證規則關聯到被驗證的屬性。 34 $scenarios = $this->scenarios(); 35 $scenario = $this->getScenario(); 36 if (!isset($scenarios[$scenario])) { 37 throw new InvalidParamException("Unknown scenario: $scenario"); 38 } 39 40 if ($attributeNames === null) { 41 $attributeNames = $this->activeAttributes(); 42 } 43 44 foreach ($this->getActiveValidators() as $validator) { 45 $validator->validateAttributes($this, $attributeNames); 46 } 47 $this->afterValidate(); 48 49 return !$this->hasErrors(); 50 }
注釋翻譯:這是一個執行數據驗證的方法,這個方法將驗證規則應用到當前場景,下面的標準用以決定規則當前是否可用。
規則必須將屬性關聯到當前場景。規則必須對當前場景有效。
這個方法前后都要調用beforeValidate和afterValidate。中間產生的錯誤可由getErrors,[[getFirstErrors()]] and [[getFirstError()]]取得。$attributeNames,用以驗證的屬性列表,如果參數為空,那意味著可應用的規則中的屬性都要被驗證。也即是這
1 public function getValidators() 2 { 3 if ($this->_validators === null) { 4 $this->_validators = $this->createValidators(); 5 } 6 return $this->_validators; 7 }注釋釋義:
個參數指定哪些是需要被驗證的,不指定就驗證所有。
場景的使用方法參考:Yii2 - 場景scenarios用法
捋一下,場景中如果沒有在后代model中重寫scenarios,那么在所有場景(action)都會執行所有rules指定字段的所有驗證(同一字段存在多種驗證,同一驗證也可能應用到多個字段)。那么場景指定各個場景要驗證的字段后,rules中就要用on關鍵字將指定場景關聯到該條rules,這樣場景跟驗證規則跟字段就一一對應起來了,如果沒有on指定場景,那就是應用到所有場景。在controller中應用某場景還需要使用$model->setScenario('update')或者$model->scenario = 'update'來應用。
$this->scenarios();中有一個方法$this->getValidators(),獲取驗證器。
返回rules中聲明的驗證器,不同于getActiveValidators的是,這個只返回應用到當前場景的驗證器。因為它的返回結果是一個ArrayObject,因此可以手動插入或移除,在model behaviors中很有用。
接下來:
1 /** 2 * Creates validator objects based on the validation rules specified in [[rules()]]. 3 * Unlike [[getValidators()]], each time this method is called, a new list of validators will be returned. 4 * @return ArrayObject validators 5 * @throws InvalidConfigException if any validation rule configuration is invalid 6 */ 7 public function createValidators() 8 { 9 $validators = new ArrayObject; 10 foreach ($this->rules() as $rule) { 11 if ($rule instanceof Validator) { 12 $validators->append($rule); 13 } elseif (is_array($rule) && isset($rule[0], $rule[1])) { // attributes, validator type 14 $validator = Validator::createValidator($rule[1], $this, (array) $rule[0], array_slice($rule, 2)); 15 $validators->append($validator); 16 } else { 17 throw new InvalidConfigException('Invalid validation rule: a rule must specify both attribute names and validator type.'); 18 } 19 } 20 return $validators; 21 }通過rules中指定的驗證規則創建相應驗證器。再看看Validator::createValidator方法。
1 /** 2 * Creates a validator object. 3 * @param string|\Closure $type the validator type. This can be either: 4 * * a built-in validator name listed in [[builtInValidators]]; 5 * * a method name of the model class; 6 * * an anonymous function; 7 * * a validator class name. 8 * @param \yii\base\Model $model the data model to be validated. 9 * @param array|string $attributes list of attributes to be validated. This can be either an array of 10 * the attribute names or a string of comma-separated attribute names. 11 * @param array $params initial values to be applied to the validator properties. 12 * @return Validator the validator 13 */ 14 public static function createValidator($type, $model, $attributes, $params = []) 15 { 16 $params['attributes'] = $attributes; 17 18 if ($type instanceof \Closure || $model->hasMethod($type)) { 19 // method-based validator 20 $params['class'] = __NAMESPACE__ . '\InlineValidator'; 21 $params['method'] = $type; 22 } else { 23 if (isset(static::$builtInValidators[$type])) { 24 $type = static::$builtInValidators[$type]; 25 } 26 if (is_array($type)) { 27 $params = array_merge($type, $params); 28 } else { 29 $params['class'] = $type; 30 } 31 } 32 33 return Yii::createObject($params); 34 }$attribute即被驗證的字段。$type指驗證類,比如required,unique等,可以是閉包,可以是model上自定義的方法,將創建一個內聯驗證器,也可以是內置的若干驗證器,或者是個帶有指定class的數組,根據那個class以及其中的驗證方法來驗證,很靈活很強大。最后一種就是直接就是指定的type本身就是一個類。這是內聯驗證器的默認方式。
1 public static $builtInValidators = [ 2 'boolean' => 'yii\validators\BooleanValidator', 3 'captcha' => 'yii\captcha\CaptchaValidator', 4 'compare' => 'yii\validators\CompareValidator', 5 'date' => 'yii\validators\DateValidator', 6 'datetime' => [ 7 'class' => 'yii\validators\DateValidator', 8 'type' => DateValidator::TYPE_DATETIME, 9 ], 10 'time' => [ 11 'class' => 'yii\validators\DateValidator', 12 'type' => DateValidator::TYPE_TIME, 13 ], 14 'default' => 'yii\validators\DefaultValueValidator', 15 'double' => 'yii\validators\NumberValidator', 16 'each' => 'yii\validators\EachValidator', 17 'email' => 'yii\validators\EmailValidator', 18 'exist' => 'yii\validators\ExistValidator', 19 'file' => 'yii\validators\FileValidator', 20 'filter' => 'yii\validators\FilterValidator', 21 'image' => 'yii\validators\ImageValidator', 22 'in' => 'yii\validators\RangeValidator', 23 'integer' => [ 24 'class' => 'yii\validators\NumberValidator', 25 'integerOnly' => true, 26 ], 27 'match' => 'yii\validators\RegularExpressionValidator', 28 'number' => 'yii\validators\NumberValidator', 29 'required' => 'yii\validators\RequiredValidator', 30 'safe' => 'yii\validators\SafeValidator', 31 'string' => 'yii\validators\StringValidator', 32 'trim' => [ 33 'class' => 'yii\validators\FilterValidator', 34 'filter' => 'trim', 35 'skipOnArray' => true, 36 ], 37 'unique' => 'yii\validators\UniqueValidator', 38 'url' => 'yii\validators\UrlValidator', 39 'ip' => 'yii\validators\IpValidator', 40 ];內聯的有以上這么多方法。
1 /** 2 * Returns the attribute names that are subject to validation in the current scenario. 3 * @return string[] safe attribute names 4 */ 5 public function activeAttributes() 6 { 7 $scenario = $this->getScenario(); 8 $scenarios = $this->scenarios(); 9 if (!isset($scenarios[$scenario])) { 10 return []; 11 } 12 $attributes = $scenarios[$scenario]; 13 foreach ($attributes as $i => $attribute) { 14 if ($attribute[0] === '!') { 15 $attributes[$i] = substr($attribute, 1); 16 } 17 } 18 19 return $attributes; 20 }返回從屬于當前場景的安全屬性名列表。
返回應用到當前場景的驗證器。$attribute是指需要返回驗證器的屬性名。如果為null,所有model中的屬性將返回。
1 foreach ($this->getActiveValidators() as $validator) { 2 $validator->validateAttributes($this, $attributeNames); 3 }這個循環對當前場景活躍屬性進行驗證,驗證代碼如下:
1 /** 2 * Validates the specified object. 3 * @param \yii\base\Model $model the data model being validated 4 * @param array|null $attributes the list of attributes to be validated. 5 * Note that if an attribute is not associated with the validator - it will be 6 * ignored. If this parameter is null, every attribute listed in [[attributes]] will be validated. 7 */ 8 public function validateAttributes($model, $attributes = null) 9 { 10 if (is_array($attributes)) { 11 $newAttributes = []; 12 foreach ($attributes as $attribute) { 13 if (in_array($attribute, $this->getAttributeNames(), true)) { 14 $newAttributes[] = $attribute; 15 } 16 } 17 $attributes = $newAttributes; 18 } else { 19 $attributes = $this->getAttributeNames(); 20 } 21 22 foreach ($attributes as $attribute) { 23 $skip = $this->skipOnError && $model->hasErrors($attribute) 24 || $this->skipOnEmpty && $this->isEmpty($model->$attribute); 25 if (!$skip) { 26 if ($this->when === null || call_user_func($this->when, $model, $attribute)) { 27 $this->validateAttribute($model, $attribute); 28 } 29 } 30 } 31 }驗證指定的對象,第一個參數$model,要驗證的model,第二個參數要被驗證的屬性列表。注意如果屬性沒有沒有關聯到驗證器會忽略,如果這個參數為空,attributes中的屬性都會被驗證。
對scenarios方法的詳解:
1 /** 2 * Returns a list of scenarios and the corresponding active attributes. 3 * An active attribute is one that is subject to validation in the current scenario. 4 * The returned array should be in the following format: 5 * 6 * ```php 7 * [ 8 * 'scenario1' => ['attribute11', 'attribute12', ...], 9 * 'scenario2' => ['attribute21', 'attribute22', ...], 10 * ... 11 * ] 12 * ``` 13 * 14 * By default, an active attribute is considered safe and can be massively assigned. 15 * If an attribute should NOT be massively assigned (thus considered unsafe), 16 * please prefix the attribute with an exclamation character (e.g. `'!rank'`). 17 * 18 * The default implementation of this method will return all scenarios found in the [[rules()]] 19 * declaration. A special scenario named [[SCENARIO_DEFAULT]] will contain all attributes 20 * found in the [[rules()]]. Each scenario will be associated with the attributes that 21 * are being validated by the validation rules that apply to the scenario. 22 * 23 * @return array a list of scenarios and the corresponding active attributes. 24 */ 25 public function scenarios() 26 { 27 $scenarios = [self::SCENARIO_DEFAULT => []]; 28 //先做一遍清空,循環類數組對象 29 foreach ($this->getValidators() as $validator) { 30 //rules中指定的on場景清空 31 foreach ($validator->on as $scenario) { 32 $scenarios[$scenario] = []; 33 } 34 //rules中指定的except場景清空 35 foreach ($validator->except as $scenario) { 36 $scenarios[$scenario] = []; 37 } 38 } 39 40 //場景變量中的第一個為self::SCENARIO_DEFAULT,后面依次為上面循環的on和except中的場景 41 //這些場景應該指活躍場景,這些場景是需要驗證的,其他不必。 42 $names = array_keys($scenarios); 43 44 //此處對驗證器循環,檢查未綁定有和排除了驗證的場景,全部置真。 45 //而未綁定有,并且不在綁定了排除驗證場景的,置真。就是說這些是需要驗證的。 46 //檢查有綁定場景,則將這些置真來驗證,其他的不驗證。 47 foreach ($this->getValidators() as $validator) { 48 if (empty($validator->on) && empty($validator->except)) { 49 foreach ($names as $name) { 50 //rules中沒有指定on和except,那$names就只有default 51 //此時會將驗證器上所有的屬性循環到$scenarios['default']中去。 52 //意即所有的屬性在所有場景都要驗證 53 foreach ($validator->attributes as $attribute) { 54 $scenarios[$name][$attribute] = true; 55 } 56 } 57 } elseif (empty($validator->on)) { 58 //rules中on為空,except設置 59 foreach ($names as $name) { 60 //不在except中,并且在rules中,在場景中設置真。不在場景中的根本不進入這個驗證列表。 61 //結果還是留下default,實際情況也是如此,所有場景都不驗證,符合邏輯 62 if (!in_array($name, $validator->except, true)) { 63 foreach ($validator->attributes as $attribute) { 64 $scenarios[$name][$attribute] = true; 65 } 66 } 67 } 68 } else { 69 //這里會留下on綁定的場景的屬性。除外的就被排除了。 70 foreach ($validator->on as $name) { 71 foreach ($validator->attributes as $attribute) { 72 $scenarios[$name][$attribute] = true; 73 } 74 } 75 } 76 } 77 78 //上面是跟rules所限定的驗證器比對,在的并符合需要驗證條件的置真。 79 //然后與場景list中的設置進行比較,過濾調場景中沒有的屬性 80 foreach ($scenarios as $scenario => $attributes) { 81 if (!empty($attributes)) { 82 $scenarios[$scenario] = array_keys($attributes); 83 } 84 } 85 86 return $scenarios; 87 }?
1 /** 2 * Returns the validators applicable to the current [[scenario]]. 3 * @param string $attribute the name of the attribute whose applicable validators should be returned. 4 * If this is null, the validators for ALL attributes in the model will be returned. 5 * @return \yii\validators\Validator[] the validators applicable to the current [[scenario]]. 6 */ 7 public function getActiveValidators($attribute = null) 8 { 9 $validators = []; 10 $scenario = $this->getScenario(); 11 foreach ($this->getValidators() as $validator) { 12 if ($validator->isActive($scenario) && ($attribute === null || in_array($attribute, $validator->getAttributeNames(), true))) { 13 $validators[] = $validator; 14 } 15 } 16 return $validators; 17 }以上返回應用到當前場景的驗證器,$attribute指定要返回的驗證器屬性名。如果為null,所有的都返回。這里先獲取到場景名,默認是default,然后循環當前model上的驗證器,如果在
當前這個場景驗證器活躍,并且傳入的屬性為null,或者該屬性存在于驗證器上,寫入驗證器數組。
1 /** 2 * Returns a value indicating whether the validator is active for the given scenario and attribute. 3 * 4 * A validator is active if 5 * 6 * - the validator's `on` property is empty, or 7 * - the validator's `on` property contains the specified scenario 8 * 9 * @param string $scenario scenario name 10 * @return bool whether the validator applies to the specified scenario. 11 */ 12 public function isActive($scenario) 13 { 14 return !in_array($scenario, $this->except, true) && (empty($this->on) || in_array($scenario, $this->on, true)); 15 }返回一個代表驗證器在給定場景和屬性上是否激活。激活的判斷標準是,驗證器的on屬性為空,或者on屬性包含指定的場景。
1 /** 2 * Returns cleaned attribute names without the `!` character at the beginning 3 * @return array attribute names. 4 * @since 2.0.12 5 */ 6 public function getAttributeNames() 7 { 8 return array_map(function($attribute) { 9 return ltrim($attribute, '!'); 10 }, $this->attributes); 11 }在開始返回不帶有!的干凈的屬性名。
?
1 /** 2 * Validates the specified object. 3 * @param \yii\base\Model $model the data model being validated 4 * @param array|null $attributes the list of attributes to be validated. 5 * Note that if an attribute is not associated with the validator - it will be 6 * ignored. If this parameter is null, every attribute listed in [[attributes]] will be validated. 7 */ 8 public function validateAttributes($model, $attributes = null) 9 { 10 if (is_array($attributes)) { 11 $newAttributes = []; 12 foreach ($attributes as $attribute) { 13 if (in_array($attribute, $this->getAttributeNames(), true)) { 14 $newAttributes[] = $attribute; 15 } 16 } 17 $attributes = $newAttributes; 18 } else { 19 $attributes = $this->getAttributeNameas(); 20 } 21 22 foreach ($attributes as $attribute) { 23 $skip = $this->skipOnError && $model->hasErrors($attribute) 24 || $this->skipOnEmpty && $this->isEmpty($model->$attribute); 25 if (!$skip) { 26 if ($this->when === null || call_user_func($this->when, $model, $attribute)) { 27 $this->validateAttribute($model, $attribute); 28 } 29 } 30 } 31 }驗證的核心代碼,驗證指定的對象,參數$model是被驗證的數據模型,$attributes要被驗證的屬性列表,如果他沒有關聯的到驗證器就忽略,如果該參數為null,所有的屬性都將被驗證。
如果是數組,定義一個空數組,并對該數組循環,傳入的就是該活躍場景下所有屬性,一般為default,全部。場景中的屬性如果不在驗證器里面,就忽略掉,不存入新數組,如果不是數組,
就直接從驗證器上取出屬性。
$skip = $this->skipOnError && $model->hasErrors($attribute)|| $this->skipOnEmpty && $this->isEmpty($model->$attribute);定義的$this->skipOnError默認為真,$this->skipOnEmpty也為真,其他兩個就是檢測是否有錯或者空。前兩個和后兩個合成的結果可以有一個為假,但必須同時為假,已經有兩個屬性默認為真的,那前后兩個其他屬性必須都為假,就是說該被驗證的屬性既不能為空,也不能有錯。
轉載于:https://www.cnblogs.com/jiangtian/p/8311066.html
總結
以上是生活随笔為你收集整理的yii验证系统学习记录,基于yiicms(一)写的太长了,再写一篇(二)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Unity3D加载资源的四种方式
- 下一篇: 计算机语言编程入门基础