user组件

早在写 yii2 实战系列的时候,我们就对 User 组件有过一定的介绍。

相信很多同学在那个时候应该就学会了怎么去实现注册,怎么去登录。但是,有部分同学,凡是遇到跟 User 组件相关的问题就直接蒙圈了,文章里面没讲,也不知道究竟怎么回事。简单列举几个

identityClass 是个什么鬼?
登录是怎么实现的?
自动登录又是怎么回事?
如何在登录成功后再保存一些其他信息?
退出是怎么实现的?
会话过期时间能不能以用户最后活动时间自动延期?
在未登录的时候,点击充值跳转去登录,登录成功之后,能不能直接跳转到充值页面?
……
等等等等,其实类似User组件的问题还有很多。再比如 Gridview 的使用,同样会涉及到很多经常会碰到的问题,怎么解决呢?

你说看 yii2 的源码吧,这玩意嘿我又看不懂。怎么办?最后只好搁浅了。

今天我们就来好好的说一说这个 User 组件。

首先,User 组件,指的是 yii\web\User,是 yii2 的核心组件之一。

我们看一下 frontend 应用对 User 组件的默认配置

1
2
3
4
5
'user' => [
'identityClass' => 'common\models\User',
'enableAutoLogin' => true,
'identityCookie' => ['name' => '_identity-frontend', 'httpOnly' => true],
],

很显然,这里默认对 yii\web\User 类的三个属性 identityClass、enableAutoLogin 和identityCookie 进行了配置。

有同学分不清 identityClass 和 yii\web\User 的关系。

简单的说一下:User 组件的作用,就是为应用提供用户认证状态的管理,通俗的说,就是跟cookie、session 打交道。真正和用户信息相关的,就是 identityClass 指向的那个类,比如说用户名、密码什么的。

下面我们以一个完整的登录退出案例,来看看 User 组件都是怎么实现的。

首先我们知道,在校验用户名、密码准备之后,我们会调用 yii\web\User::login 方法完成登录操作,大致是这样的

1
Yii::$app->user->login($this->getUser(), 3600 * 24 * 30);

找到 yii\web\User::login 方法,我们看到,该方法的第一个参数是说该方法只接受 yii\web\IdentityInterface 接口的实例。这也就是为什么我们在配置 User 组件的 identityClass 属性的时候,让该属性指向的类 implements IdentityInterface 的原因。第二个参数是保存登录状态的时间,单位是秒,这个跟“自动登录”有关系,等一下我们详细说明。

我们看一下 yii\web\User::login 方法做了哪些事。

首先是 yii\web\User::beforeLogin 方法的调用,这里会触发 yii\web\User::EVENT_BEFORE_LOGIN 事件,User 组件相关的事件定义以及触发,我们之前也有过描述。

随后,调用 yii\web\User::switchIdentity 方法切换用户身份,User组件实现的是用 session或者cookie来存储用户身份信息。所以,如果我们配置 User 组件的 enableSession 属性为 false,自然是无法利用 session 或者 cookie 来操作的,这在 rest api 应用中最为常见,rest api 中我们常用 token 认证,在实现系列中我们也详细的进行过讲解。

找到 yii\web\User::switchIdentity 方法我们继续分析。

yii\web\User::setIdentity 方法,在程序运行时标识用户的状态信息,从 yii\web\User::getIdentity 方法可以看出,此举避免了我们重复调用 yii\web\User::renewAuthStatus 方法更新 session 和 cookie 里保存的信息。这一点在开发过程中值得借鉴。

yii\web\User::enableAutoLogin 属性,main.php 文件内我们有对该属性进行过配置,这个属性,表示的是是否开启自动登录,自动登录的实现是基于cookie 的,所以说在切换用户身份状态的时候,如果我们开启了自动登录,这里便会调用 yii\web\User::removeIdentityCookie 方法对其进行移除。

一切准备就绪之后,下面便利用 session 存储用户的身份信息。

我们看一下都存储了哪些信息。

1
2
3
4
5
6
7
$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);
}

首先是 yii\web\User::idParam ,这里存储的是 common\models\User::getId 方法的返回值,要注意的是这个返回值一定要跟 common\models\User::findIdentity 方法的参数保持一致,这一点我们可以在后面更新用户身份的时候(yii\web\User::renewAuthStatus 调用的时候)得到验证。

存储好 id 之后,这里有两个时间的设置,先简单了解一下这两个属性

yii\web\User::authTimeout,如果用户不活动了,将会在该秒数之后自动退出,
yii\web\User::absoluteAuthTimeout,不管用户活不活动,时间到了就退出
用户活动指的是用户有访问我们的应用。二者具体的含义,稍后我们会详细说明。

我们在登录大部分网站的时候,应该都碰过过勾选“自动登录”的复选框,其含义就是下次用户访问我们网站的时候,不用再重新登录了。具体的实现,有必要看一下下面这段代码的实现

1
2
3
if ($duration > 0 && $this->enableAutoLogin) {
$this->sendIdentityCookie($identity, $duration);
}

$duration,在调用 yii\web\User::login 的时候我们传递过来的第二个参数。

yii\web\User::enableAutoLogin,配置 User 组件支不支持自动登录,并不是你登录界面传递过来的 checkout 的值。

创建user数据表的时候,有同学就说搞不清楚 auth_key 字段是干嘛的,这不,这里就用到了。

假设用户勾选了“自动登录”的复选框,那么 yii\web\User::sendIdentityCookie 方法在这里便会被调用。该方法其实就是把用户标识、auth_key 以及认为“自动登录”的有效时间 duration 保存到cookie中。

yii\web\User::switchIdentity 方法就到这里,之后,我们回到 yii\web\User::login 方法,继续看看登录的时候还做了什么事。

这里有一段 log 的记录,再者便是调用 yii\web\User::afterLogin 方法触发 yii\web\User::EVENT_AFTER_LOGIN 事件。

至此,登录的操作也便结束了。

总结两句话

session 存储用户标识,允许的情况下,再存储两个认证时间,这两个认证时间跟用户活动不活动有很大关系
如果勾选了“自动登录”复选框,会用 cookie 存储用户的身份信息
下面我们重点看一下,在服务端和客户端交互的过程中,到底又是如何知道用户登录的呢?

这个大家其实并不陌生,我们很早就接触过 ACF ,ACF 判断用户登录状态的依据主要是 yii\web\User::getIsGuest 方法,这个有兴趣的话可以看看 yii\filters\AccessControl 和 yii\filters\AccessRule 的实现。

大家找到 yii\web\User::getIsGuest 方法,我们看到,该方法的判断依据取决于 yii\web\User::getIdentity 方法的返回值。

yii\web\User::getIdentity 方法的返回值,是 yii\web\User::_identity 属性,我们知道,如果用户正确的登录了,该属性的值肯定是真,我们看一下在开启session 的情况下,yii\web\User::renewAuthStatus 方法会被调用。

这个方法很重要了,我们在登录操作中保存到 session 的数据,都会在这个方法内利用,这样我们才能知道登录时存储的数据到底有什么意义。

下面分几种情况,来看一下 yii\web\User::renewAuthStatus 方法的实现。

1、我们先假设用户已经登录了,那么 $id 和 $identity,自然都为真。当然也不一定,万一 我手动把 session 数据删除了,$id 就是null , $identity 也是null,这种我们放第二种情况分析,暂时我们先认为二者都为真。

yii\web\User::setIdentity 方法被调用之后,我们标记下代码,方便分析。

1
2
3
4
5
6
7
8
9
// ②
if ($identity !== null && ($this->authTimeout !== null || $this->absoluteAuthTimeout !== null)) {

}

// ③
if ($this->enableAutoLogin) {

}

首先看②,如果要执行②,yii\web\User::authTimeout 或者 yii\web\User::absoluteAuthTimeout 必须被设置过。假设被设置过,那么登录的时候二者就被存储到 session 了,这一点没错。

我们看一下 ②部分的代码,会把这两个时间值获取到,当然一般情况一个项目不可能会同时设置两个时间值。从需求的角度来说,如果我们设置了 yii\web\User::absoluteAuthTimeout 等于 3600 秒,从下面这行代码的判断上我们可以看出,假设用户9点钟登录了系统,那么10点一到,便会调用 yii\web\User::logout 方法退出系统。

1
2
3
if ($expire !== null && $expire < time() || $expireAbsolute !== null && $expireAbsolute < time()) {
$this->logout(false);
}

如果我们设置了 yii\web\User::authTimeout 等于 3600 秒,这并不会让用户退出系统,而是更新了用户最终的退出时间,假设用户9.30分刷新了系统,那么 yii\web\User::authTimeout 的值便是在 9.30 分的基础上再加一个小时,即10.30分。如果到了10.30 分用户还没发起过任何请求,才会认为被退出。

1
$session->set($this->authTimeoutParam, time() + $this->authTimeout);

很显然,这里建议大家使用 yii\web\User::authTimeout ,相对而言更友好一些。当然,真实情况还要取决于产品的需求了。

我们接着看③部分的代码。

③部分的代码中可以看出,如果用户已经登录且 yii\web\User::autoRenewCookie 属性值为真,yii\web\User::renewIdentityCookie 方法会被调用,该方法会更新 cookie 中保存的用户身份信息。

假设用户是访客,那么下面这段代码的返回值 $id 和 $identity 也肯定都是 null 了

1
2
3
4
5
$id = $session->getHasSessionId() || $session->getIsActive() ? $session->get($this->idParam) : null;

if ($id === null) {
$identity = null;
}

下面我们直接跳到最后一个 if 判断,此时 yii\web\User::loginByCookie 方法会被调用,即通过 cookie 登录。cookie 保存的用户信息来源于你打勾勾选的“自动登录”操作。实际获取cookie 信息的操作主要由yii\web\User::getIdentityAndDurationFromCookie 方法的实现。

所以建议大家在使用框架的时候,cookie一定要加密处理,在 yii2 中默认是经过处理的。

以上,其实便是整个登录的过程。

有一点忘记说了,如果想额外保存一些用户的其他信息,你可以在登录操作之前,写好 yii\web\User::EVENT_AFTER_LOGIN 事件处理程序,登录的时候触发即可。

下面我们再看一下退出操作是怎么实现的。

退出比较简单,主要取决于 yii\web\User::logout 方法的实现。

退出前和退出后,会有两个事件 yii\web\User::EVENT_BEFORE_LOGOUT 和 yii\web\User::EVENT_AFTER_LOGOUT 事件的触发。

User 组件相关的 session 数据的清理,通过执行 yii\web\User::switchIdentity(null) 即可。

至于 session 会话的数据,则要依赖 Yii::$app->getSession()->destroy(); 方法的调用了。

后面可能还有一些小问题,比如我们开篇提到的第九条:在未登录的时候,点击充值跳转去登录,登录成功之后,能不能直接跳转到充值页面?

答案肯定可以,但是不需要我们手动实现,User 组件就提供了这样的功能。

我们只需要在充值操作中,调用一下 yii\web\User::setReturnUrl(Yii::$app->getRequest()->getUrl()) 方法把充值地址临时保存到 session 中,登录之后,调用 yii\web\User::getReturnUrl 方法获取 url 进行跳转即可。

user 组件暂时就介绍这么多,如果大家在实际操作中遇到好的问题,也可以留言分享,我们共同探讨。