1.使用方法
要设置子模块单独登陆,需要在各个模块中独立配置user组件,并配置idParam属性
主模块components中配置
'user' => [
'class'=>'\yii\web\User',
'identityClass' => 'backend\models\Admin',
]
主模块中获取user组件\Yii::$app->user
子模块components中配置
'user' => [
'class'=>'\yii\web\User',//子模块中user组件class属性不能省略,不然会报错
'identityClass' => 'frontend\models\User',
'idParam'=>'frontId',
]子模块中获取user组件\Yii::$app->controller->module->user(控制器中$this->module->user)。多模块登陆方法到此为 止。不想看分析的可以就此打住。
2.源码分析
2.1 user组件重要属性
enableSession:是否允许用session保存账号id,实现session保存登陆状态
enableAutoLogin: 是否允许cookie自动登陆,只有enableSession为true时生效。另外在yii\web\User::login中 ($identity,$duration),$duration表示cookie有效时间,这有这个值大于0,cookie登陆才有效 $duration表示cookie有效时间,这有这个值大于0,cookie登陆才有效
autoRenewCookie:是否自动刷新cookie有效时间
idParam: session中存放账号id的key,不同模块登陆就靠这个属性了
identityClass : 账号AR类,必须实现接口IdentityInterface
authTimeoutParam: session中保存idParam的相对过期时间的key
authTimeout: authTimeoutParam的值
absoluteAuthTimeoutParam:session中保存idParam的绝对过期时间的key
absoluteAuthTimeout: absoluteAuthTimeoutParam的值
解释下相对过期时间与绝对过期时间:
每次在发起新请求后,如果当前时间小于相对过期时间,同时小于绝对过期时间,那么就把相对过期时间 authTimeoutParam的值加上authTimeout,绝对过期时间absoluteAuthTimeoutParam的值保持不变。如果当前时间小于相对过期时间,但是大于等于绝对过期时间,那么认为登陆过期。比如相对过期时间为30分钟,绝对过期时间为60分钟,在12:00点首次登陆,在enableSession为true的情况下,设置authTimeoutParam为当前时间12:00上+30分钟,absoluteAuthTimeoutParam为当前时间12:00上+60分钟。10分钟后请求再次到来,那么相对过期时间authTimeoutParam在当前时间12:10上加30分钟,绝对时间不变,就这样,不断请求,authTimeoutParam不断递增,但是absoluteAuthTimeoutParam始终没有变化,终于当某次请求距第一次请求大于等于absoluteAuthTimeoutParam,即13:00的时候,就会登陆失效。当然如果在13:00之前,距离上一次登陆时间超过了30分钟,那也过期了。
注意:相对过期时间与绝对过期时间不是必须的。可以都不设置,也可以只设置一个。如果都没有设置,那么登陆是否失效取决于session失效时间,否则取决于session失效时间和相对过期时间与绝对过期时间。
2.2 代码分析
1)在login(IdentityInterface $identity, $duration = 0)方法中,需要传入账号AR类的一个实例,如果要实现cookie登陆,还需要$duration 大于 0,但是首先得配置enableAutoLogin为true。在登陆前后会分别触发beforeLogin与afterLogin事件,afterLogin事件中可以做登陆成功的日志功能,比如更新当前登录时间,当前登录ip。
//登录前事件
$this->beforeLogin($identity, false, $duration);
//实际登录
$this->switchIdentity($identity, $duration);
//登录后事件
$this->afterLogin($identity, false, $duration);
2)switchIdentity中判断了是否允许session保存会话状态,是否允许cookie自动登陆,登陆是否失效,刷新相对过期时间,删除session,cookie。
//设置$_identity属性,$_identity=$identity
$this->setIdentity($identity);
//如果不允许session保存状态,那就不继续后续操作了
if (!$this->enableSession) {
return;
}
//清除cookie,在调用logout的时候也会调用switchIdentity方法。这里清除cookie和session就起到作用了
if ($this->enableAutoLogin) {
$this->removeIdentityCookie();
}
//清除session
$session = Yii::$app->getSession();
$session->remove($this->idParam);
$session->remove($this->authTimeoutParam);
//如果$identity不为null,即查询到了账号
if ($identity) {
//设置账号id的session,key为$this->idParam,这句很重要哦$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);}
//设置cookie,这里体现了$duration必须大于0并且$this->enableAutoLogin为true,才能写cookie
if ($duration > 0 && $this->enableAutoLogin) {
$this->sendIdentityCookie($identity, $duration);
}
3)如果允许自动登录,就会执行sendIdentityCookie,写cookie
$cookie = new Cookie($this->identityCookie);
$cookie->value = json_encode([
$identity->getId(),//账号id
$identity->getAuthKey(),//账号的authkey字段,由账号AR类实现IdentityInterface的getAuthKey方法
$duration,
], JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
//设置cookie的过期时间
$cookie->expire = time() + $duration;
Yii::$app->getResponse()->getCookies()->add($cookie);
4)在代码中,我们经常使用\Yii::$app->getUser()->getIsGuest(),\Yii::$app->getUser()->getIdentity()获取登录信息。其实getIsGuest也是判断getIdentity返回值是否为null,所以重点还是getIdentity()。getIdentity()方法如下:
//$_identity默认值为false,因而在代码中首次获取登录信息的时候,会执行renewAuthStatus(),并在renewAuthStatus方法中设置$_identity
if ($this->_identity === false) {
//如果enableSession 为false,那么就没办法获取登录信息了,无论是seesion或者cookie都不行
if ($this->enableSession && $autoRenew) {
$this->_identity = null;
$this->renewAuthStatus();
} else {
return null;}
}
return $this->_identity;
5)renewAuthStatus从session或者cookie中登录
$session = Yii::$app->getSession();
//从session中获取$this->idParam的值,即账号id
$id = $session->getHasSessionId() || $session->getIsActive() ? $session->get($this->idParam) : null;
//如果session过期了
if ($id === null) {
$identity = null;
} else {/* @var $class IdentityInterface */
//否则查询数据库
$class = $this->identityClass;
//findIdentity由IdentityInterface提供,并由账号AR类实现,用于user组件查询账号
$identity = $class::findIdentity($id);
}//设置$_identity属性,等于null或则账号AR类的一个实例
$this->setIdentity($identity);
//如果查询到了账号,就要再次判断是否过期。这里的过期不是session过期,是由user组件维护的一种过期
if ($identity !== null && ($this->authTimeout !== null || $this->absoluteAuthTimeout !== null)) {
//相对过期
$expire = $this->authTimeout !== null ? $session->get($this->authTimeoutParam) : null;
//绝对过期
$expireAbsolute = $this->absoluteAuthTimeout !== null ? $session->get($this->absoluteAuthTimeoutParam) : null;
//任何一个过期,那就是过期了
if ($expire !== null && $expire < time() || $expireAbsolute !== null && $expireAbsolute < time()) {
$this->logout(false);
} elseif ($this->authTimeout !== null) {
//如果没有过期,并且设置了相对过期,那就刷新相对过期截至时间
$session->set($this->authTimeoutParam, time() + $this->authTimeout);
}
}
//如果设置了cookie自动登录if ($this->enableAutoLogin) {
//如果在上面$identity=null的话(session过期或者没查询到账号或者seesion存在但是相对绝对时间过期),
//那就尝试从cookie登录
if ($this->getIsGuest()) {
$this->loginByCookie();
} elseif ($this->autoRenewCookie) {
//如果设置了cookie登录,并且autoRenewCookie为true,就刷新cookie有效期,
$this->renewIdentityCookie();
}
}
6)loginByCookie,从cookie中登录
$value = Yii::$app->getRequest()->getCookies()->getValue($this->identityCookie['name']);
//cookie都不存在,那就没啥可说的
if ($value === null) {
return;
}
$data = json_decode($value, true);
//对cookie做一个检查,$data[0], $data[1], $data[2]分别对应账号id,账号authkey,有效期
if (count($data) !== 3 || !isset($data[0], $data[1], $data[2])) {
return;
}
list ($id, $authKey, $duration) = $data;/* @var $class IdentityInterface */
//查询
$class = $this->identityClass;$identity = $class::findIdentity($id);
//没查询到账号
if ($identity === null) {
return;
} elseif (!$identity instanceof IdentityInterface) {
throw new InvalidValueException("$class::findIdentity() must return an object implementing IdentityInterface.");
}
//查询到了账号,并且验证成功,validateAuthKey由IdentityInterface提供,由账号AR类实现,验证authkeyif ($identity->validateAuthKey($authKey)) {
//验证通过
if ($this->beforeLogin($identity, true, $duration)) {
//又回到第二步,设置session,设置$_identity
$this->switchIdentity($identity, $this->autoRenewCookie ? $duration : 0);$this->afterLogin($identity, true, $duration);
}
} else {
Yii::warning("Invalid auth key attempted for user '$id': $authKey", __METHOD__);
}
7)在第5步中,如果相对过期或者绝对过期任何一个过期了,会执行logout退出登录,然后触发退出事件。logout中:
//获取账号信息,如果session没有过期,而是相对过期或者绝对过期,并且查询到了账号,那么$identity是不为null的
$identity = $this->getIdentity();
if ($identity !== null && $this->beforeLogout($identity)) {
//设置$_identity=null
$this->switchIdentity(null);if ($destroySession && $this->enableSession) {
Yii::$app->getSession()->destroy();
}
$this->afterLogout($identity);
}
return $this->getIsGuest();
设置了$_identity=null后,那么在通过getIdentity()获取账号信息的时候,因为$_identity不为false,所以会直接返回null,即账号信息为null,表示没有登录。
总结:
1.账号AR类必须实现IdentityInterface接口
2.要实现session保存登陆状态,需要设置enableSession=true,默认如此不设置也没关系,实现findIdentity,getId。
3.要实现cookie自动登陆,需要设置enableSession=true,enableAutoLogin=true,并且在login()方法中,第二个参数持续时间需大于0,并且实现validateAuthKey,getAuthKey。
4.登陆能在session失效之上,再加一层失效时间限制,通过设置authTimeout,absoluteAuthTimeout。
5.从session中获取账号id,是通过idParam属性的值作为key。这个就是不同模块单独登陆的前提。模块要单独登录,设置不同模块的idParam。在各模块中获取user组件需要利用\Yii::$app->controller->module->user(子模块中不能用getUser,用了会报错,这点我也不知道原因)。
yii2多模块独立登录,与登录源码分析
版权声明:本文为kkyy3210原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。