Yii2.0登录详解(下)
在上一篇博文中,笔者讲述了yii2应用用户登陆的基本方法,但是这些方法到底是怎样实现登陆的呢?底层的原理到底是什么?在这篇博文笔者将从Yii的源码角度分析登陆的基本原理以及cookie自动登陆的原理,通过源码的分析,各位对Yii的理解也会更上一层楼。
一、第一次正常登陆
1、在LoginForm.PHP中,我们曾经调用了这个方法:
- Yii::$app->user->login($this->getUser(), $this->rememberMe ? 3600*24*30 : 0);
由以上可知,调用了user组件的login()方法,把两个参数传递进入,分别是User类实例以及记住时间(Cookie验证会用到,如果传递0,则不启用Cookie验证)。
2、进入yii\web\user类中,找到login()方法如下所示:
- public function login(IdentityInterface $identity, $duration = 0)
- {
- if ($this->beforeLogin($identity, false, $duration)) {
- $this->switchIdentity($identity, $duration); //①
- $id = $identity->getId();
- $ip = Yii::$app->getRequest()->getUserIP();
- if ($this->enableSession) {
- $log = "User '$id' logged in from $ip with duration $duration.";
- } else {
- $log = "User '$id' logged in from $ip. Session not enabled.";
- }
- Yii::info($log, __METHOD__);
- $this->afterLogin($identity, false, $duration);
- }
- return !$this->getIsGuest();
- }
这里关注①号处代码:$this->switchIdentity($identity,$duration).这里调用了当前类的switchIdentity方法,把接受到的两个参数同时传递进去,我们往下看:
3、switchIdentity($identity,$duration)方法如下:
- public function switchIdentity($identity, $duration = 0)
- {
- <span style="white-space:pre"> </span>...
- <span style="white-space:pre"> </span>...
- if ($identity) {
- ...
- if ($duration > 0 && $this->enableAutoLogin) {<span style="white-space:pre"> </span>//①
- $this->sendIdentityCookie($identity, $duration); //②
- }
- } elseif ($this->enableAutoLogin) {
- Yii::$app->getResponse()->getCookies()->remove(new Cookie($this->identityCookie));
- }
- }
由于代码过长,笔者做了适当精简,只讨论与登陆和cookie联系最为密切的部分,由①处代码,首先会对duration进行判断,只有大于0的情况下才会进行cookie验证,然后再判断了enableAutoLogin的值,这个值也是cookie验证的关键所在,只有为true的时候才会储存cookie,该值在config/main.php中注册user组件的时候进行初始化,代码如下:
- 'components' => [
- 'user' => [
- 'identityClass' => 'app\modules\backend\models\User',
- 'enableAutoLogin' => true,
- ],]
在判断都为真的时候,即进行cookie储存和登陆的时候,进入②号代码,可以看到,调用了sendIdentityCookie()方法。
4、sendIdentityCookie($identity,$duration):
- protected function sendIdentityCookie($identity, $duration)
- {
- $cookie = new Cookie($this->identityCookie);
- //cookie的value值是json数据
- $cookie->value = json_encode([
- $identity->getId(),
- $identity->getAuthKey(),
- $duration,
- ], JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
- //设置cookie的有效时间
- $cookie->expire = time() + $duration;
- Yii::$app->getResponse()->getCookies()->add($cookie); //①
- }
在生成一个cookie的实例以及对cookie进行初始化后,接下来来到了重点部分,即①号代码处,首先getResponse()得到response组件,然后调用response的getCookies方法,返回一个CookieCollection实例,该实例保存了response组件所生成的所有cookie,显然,最后一个add()方法是把当前设置好的cookie保存进CookieCollection实例中。
到目前为止,yii已经完成了储存cookie的操作,但是,有一点要注意的,利用yii框架的这种方式储存cookie,要记住一点,就是在Controller那里,通过验证后,应该使用redirect()来进行页面跳转,而不应该直接render()渲染布局,否则,当前登陆完成后,cookie将暂时得不到保存,(详细可通过查看浏览的cookie缓存来查看)如果cookie得不到立即的储存,有可能对后续用户的登陆造成未知困扰。
为什么会出现这样的情况呢?
我们来看看response组件的redirect()方法,(一般通过Yii::$app->response->redirect()来跳转url):
- public function redirect($url, $statusCode = 302, $checkAjax = true)
- {
- ...
- ...
- return $this;
- }
关键在于最后的return $this;这句表示把当前类作为整个反应返回给客户端,换句话说:即当前保存的所有cookie,以及各种其他属性,在调用了redirect后全部返回。所以,我们在控制器需要使用redirect(),而不是直接使用render()方法。
综上所述,在完成了user组件的login()方法后,用户的个人信息便保存于user组件中,直到用户关闭浏览器或者退出登录。
二、利用Cookie登陆
在用户关闭浏览器的时候(非退出),再次访问登陆页面,会发现页面已经自动跳转到主页,也即是说完成了自动登陆的功能(前提是点击了Remember Me),我们回顾一下logincontroller里面关于登陆的逻辑:
- if (!\Yii::$app->user->isGuest) {
- return $this->goHome();
- }
可以看出,当访问登陆页面的时候,会先执行判断,判断当前用户是否是游客,若不是,则直接跳转到主页。所以关于自动登陆的逻辑便隐藏在:Yii::$app -> user ->isGuest 中,我们查看user组件相关代码:
- public function getIsGuest()
- {
- return $this->getIdentity() === null;
- }
在以上方法中,调用了getIdentity()方法:
- public function getIdentity($autoRenew = true)
- {
- if ($this->_identity === false) {
- if ($this->enableSession && $autoRenew) {
- $this->_identity = null;
- $this->renewAuthStatus();
- } else {
- return null;
- }
- }
- return $this->_identity;
- }
由于user组件默认是开始session的,所以enableSession应该为true,所以会执行renewAuthStatus()函数:
- protected function renewAuthStatus()
- {
- ...
- if ($this->enableAutoLogin) {
- if ($this->getIsGuest()) {
- $this->loginByCookie();
- } elseif ($this->autoRenewCookie) {
- $this->renewIdentityCookie();
- }
- }
- }
结合之前的enableAutoLogin为true以及当前处于未登录状态,所以getIsGuest()返回真,所以最后会执行loginByCookie()方法,这也是核心所在:
- protected function loginByCookie()
- {<span style="white-space:pre"> </span>//从客户端读取cookie
- $value = Yii::$app->getRequest()->getCookies()->getValue($this->identityCookie['name']);
- if ($value === null) {
- return;
- }
- <span style="white-space:pre"> </span>//由于之前储存cookie是用json格式储存,所以现在需要先解析
- $data = json_decode($value, true);
- if (count($data) !== 3 || !isset($data[0], $data[1], $data[2])) {
- return;
- }
- list ($id, $authKey, $duration) = $data;
- /* @var $class IdentityInterface */
- $class = $this->identityClass;<span style="white-space:pre"> </span>//读取当前的用户验证类类名,即实现了Identity接口的类
- $identity = $class::findIdentity($id);<span style="white-space:pre"> </span>//调用该类的方法,从数据库查找数据
- if ($identity === null) {
- return;
- } elseif (!$identity instanceof IdentityInterface) {
- throw new InvalidValueException("$class::findIdentity() must return an object implementing IdentityInterface.");
- }
- <span style="white-space:pre"> </span>//如果数据库提供的auth_key与从客户取得的auth_key相同
- if ($identity->validateAuthKey($authKey)) {
- if ($this->beforeLogin($identity, true, $duration)) { //①
- $this->switchIdentity($identity, $this->autoRenewCookie ? $duration : 0);
- $ip = Yii::$app->getRequest()->getUserIP();
- Yii::info("User '$id' logged in from $ip via cookie.", __METHOD__);
- $this->afterLogin($identity, true, $duration);
- }
- } else {
- Yii::warning("Invalid auth key attempted for user '$id': $authKey", __METHOD__);
- }
- }
由①可知,在auth_key验证通过后,所执行的代码基本与首次执行的login()方法相同,即实现了重新登录的功能。
关于Yii2用户登陆功能的实现以及cookie自动登陆的原理已经全部讲述完毕。
ps:本文转自他人,不是原创,我还没那技术。
Yii2.0登录详解(下)的更多相关文章
- linux ssh使用深度解析(key登录详解)
linux ssh使用深度解析(key登录详解) SSH全称Secure SHell,顾名思义就是非常安全的shell的意思,SSH协议是IETF(Internet Engineering Task ...
- windows版mysql8.0安装详解
2018年07月04日 13:37:40 Zn昕 阅读数 6433更多 分类专栏: mysql 版权声明:本文为博主原创文章,遵循CC 4.0 by-sa版权协议,转载请附上原文出处链接和本声明. ...
- Android消息传递之EventBus 3.0使用详解
前言: 前面两篇不仅学习了子线程与UI主线程之间的通信方式,也学习了如何实现组件之间通信,基于前面的知识我们今天来分析一下EventBus是如何管理事件总线的,EventBus到底是不是最佳方案?学习 ...
- [转载]AxureRP 7.0部件详解(一)
本文为Axure RT7.0教程,本章主要介绍menu菜单.table表格.Tree Widget 树部件三个部件,后续将持续更新...... Menu 菜单 常用案例 网站导航菜单部件通常用于母板之 ...
- Bmob第三方登录详解
Bmob第三方登录详解 Bmob 第三方登录 简介 本文主要介绍新浪微博,QQ,微信的登录接入以及如何配合BmobSDK中的第三方登录功能实现第三方登录. 在使用之前请先按照快速入门创建好可以调用Bm ...
- Cocos2d-x 3.0坐标系详解(转载)
Cocos2d-x 3.0坐标系详解 Cocos2d-x坐标系和OpenGL坐标系相同,都是起源于笛卡尔坐标系. 笛卡尔坐标系 笛卡尔坐标系中定义右手系原点在左下角,x向右,y向上,z向外,OpenG ...
- [js高手之路]深入浅出webpack教程系列3-配置文件webpack.config.js详解(下)
本文继续接着上文,继续写下webpack.config.js的其他配置用法. 一.把两个文件打包成一个,entry怎么配置? 在上文中的webpack.dev.config.js中,用数组配置entr ...
- NPOI2.2.0.0实例详解(十)—设置EXCEL单元格【文本格式】 NPOI 单元格 格式设为文本 HSSFDataFormat
NPOI2.2.0.0实例详解(十)—设置EXCEL单元格[文本格式] 2015年12月10日 09:55:17 阅读数:3150 using System; using System.Collect ...
- Vue1.0用法详解
Vue.js 不支持 IE8 及其以下版本,因为 Vue.js 使用了 IE8 不能实现的 ECMAScript 5 特性. 开发环境部署 可参考使用 vue+webpack. 基本用法 1 2 3 ...
随机推荐
- UDT中epoll对CLOSE状态的处理
epoll_wait()返回可用uid时,对uid取状态,本该是BROKEN的,却取到CLOSED,然而,不能像处理BROKEN事件那样处理CLOSED事件,这样移除不了CLOSED事件,于是epol ...
- ubuntu kylin 14.04编译openjdk-7u40
1.ubuntu kylin 14.04安装依赖 sudo apt-get install build-essential gawk m4 libasound2-dev libcups2-dev li ...
- js中取得当前加载的js的src地址
在很多js框架中看到过,如果要动态加载框架内部的其他js,加载的时候加载的地址经常是一个相对的地址,只能是这样了哦,因为框架根本不知道用此框架的用户,将框架js文件放的具体目录,所以框架中一般会采用如 ...
- 复制粘贴出来的悲剧----spring实现文件下载和HttpStatus.CREATED
今天真是被自己的懒惰和复制粘贴给坑惨了... 网上有这么一个spring下载文件的最佳实践: @RequestMapping("download") public Response ...
- 3、DNS服务器功能(正向、反向解析)
实验目的: 建立gr.org域的主名称服务器.解析: 名称 IP 用途 ns.gr.org 192.168.170.3 名称服务器 www.gr.org 192 ...
- ECharts 初体验
缘起 ECharts,缩写来自Enterprise Charts,商业级数据图表,它最初是为了满足公司商业体系里各种业务系统(如凤巢.广告管家等等)的报表需求.以前这些系统的图表需求我们都是使用fla ...
- 虚拟机linux上网问题
VMware中虚拟机网络的三种设置 1.桥接(bridged)(设置上网比较繁琐,但是在嵌入式开发中比较有用)2.NAT(上网比较简单,但是不能用于嵌入式开发中)3.Host only (该模式下仅主 ...
- POJ 1325 Machine Schedule——S.B.S.
Machine Schedule Time Limit: 1000MS Memory Limit: 10000K Total Submissions: 13731 Accepted: 5873 ...
- 第32课 Qt中的文件操作
1. Qt的中IO操作 (1)Qt中IO操作的处理方式 ①Qt通过统一的接口简化了文件和外部设备的操作方式 ②Qt中的文件被看作一种特殊的外部设备 ③Qt中的文件操作与外部设备的操作相同 (2)IO操 ...
- mongodb基本操作的学习
1.基本操作: 如何安装?创建存放数据的文件夹 robomongo: 图形化管理工具 create -->save -->connect 创建数据库:use Database_name 检 ...