yii2多模块独立登录,与登录源码分析

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类实现,验证authkey

if ($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,用了会报错,这点我也不知道原因)。

        

版权声明:本文为kkyy3210原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。