Zhucola 2019-07-02 17:10:21 3665次浏览 0条评论 4 2 0

文章已经同步到 GitHub 仓库 https://github.com/Zhucola/yii_core_debug ,欢迎 star~~

目录

整体逻辑流程

获取认证信息

  • 从session获取键为 __id 的值
  • 通过 __id 保存的id去查对应的具体用户认证信息
  • 判断session中的 __expire__absoluteExpire 是否大于当前时间戳,有大于则说明会话过期,直接进行logout退出操作
  • 延迟 __expire 会话中的时间戳
  • 如果session中没认证信息就去查cookie
  • 从cookie中获取_identity对应的id
  • 通过_identity的id去查对应的具体用户认证信息
  • 判断cookie中的AuthKey是否与认证信息一致
  • 如果cookie中的AuthKey不一致或者cookie中存的信息不合法则删除cookie
  • 如果session中有认证信息则延长cookie的生存周期

退出

  • 删cookie
  • 删sesison中的 __expire__absoluteExpire
  • 更新session_id
  • session_destory

注册认证信息

  • 删sesison中的 __expire__absoluteExpire
  • 更新session_id
  • 设置sesison中的 __expire__absoluteExpire
  • 设置cookie

认证组件源码简单介绍

yii封装了一个用户认证类,该类是一个可以在配置文件web.php中配置的组件
默认配置如下

'components' => [
  ...
  'user' => [
      'identityClass' => 'app\models\User',
      'enableAutoLogin' => true,
  ],
  ...
]

认证组件的class属性在入口文件执行底层base\Application合并

foreach ($this->coreComponents() as $id => $component) {
    if (!isset($config['components'][$id])) {
        $config['components'][$id] = $component;
    } elseif (is_array($config['components'][$id]) && !isset($config['components'][$id]['class'])) {
        $config['components'][$id]['class'] = $component['class'];
    }
}

所以默认的配置为

'components' => [
  ...
  'user' => [
      'class' => 'yii\web\User',
      'identityClass' => 'app\models\User',
      'enableAutoLogin' => true,
  ],
  ...
]

yii\web\User继承Component,所以可以使用属性注入、事件和行为

class User extends Component
{
    const EVENT_BEFORE_LOGIN = 'beforeLogin';
    const EVENT_AFTER_LOGIN = 'afterLogin';
    const EVENT_BEFORE_LOGOUT = 'beforeLogout';
    const EVENT_AFTER_LOGOUT = 'afterLogout';

可用的属性注入有

protected function getAccessChecker()
protected function getAuthManager()
protected function getIdentityAndDurationFromCookie()
public function setReturnUrl($url)
public function getReturnUrl($defaultUrl = null)
public function getId()
public function getIsGuest()
public function setIdentity($identity)
public function getIdentity($autoRenew = true)

需要注意的是user组件有init方法,这个方法是底层方法,就是执行完构造函数后会由底层直接调用

public function init()
{
    parent::init();
    //是否有认证逻辑类
    if ($this->identityClass === null) {
        throw new InvalidConfigException('User::identityClass must be set.');
    }
    //如果用cookie存信息的话,需要有cookie相关的属性
    if ($this->enableAutoLogin && !isset($this->identityCookie['name'])) {
        throw new InvalidConfigException('User::identityCookie must contain the "name" element.');
    }
    if (!empty($this->accessChecker) && is_string($this->accessChecker)) {
        $this->accessChecker = Yii::createObject($this->accessChecker);
    }
}

identityClass简单源码介绍

User组件有一个核心属性是identityClass,identityClass类其实就是获取用户信息,一般都会封装成和数据库关联的,从数据库直接获取用户信息,为了方便起见我们使用yii安装后的默认identitiyClass属性app\models\User
值得注意的是,identityClass必须实现接口IdentityInterface
默认的identityClass内部源码非常简单

private static $users = [
    '100' => [
        'id' => '100',
        'username' => 'admin',
        'password' => 'admin',
        'authKey' => 'test100key',
        'accessToken' => '100-token',
    ],
    '101' => [
        'id' => '101',
        'username' => 'demo',
        'password' => 'demo',
        'authKey' => 'test101key',
        'accessToken' => '101-token',
    ],
];
//通过id获取用户信息,就是静态user属性的key
public static function findIdentity($id)
{
    return isset(self::$users[$id]) ? new static(self::$users[$id]) : null;
}
//通过accessToken获取用户信息
public static function findIdentityByAccessToken($token, $type = null)
{
    foreach (self::$users as $user) {
        if ($user['accessToken'] === $token) {
            return new static($user);
        }
    }

    return null;
}
//通过用户名获取用户属性,默认是不区分大小写的
public static function findByUsername($username)
{
    foreach (self::$users as $user) {
        if (strcasecmp($user['username'], $username) === 0) {
            return new static($user);
        }
    }

    return null;
}
//判断authKey是否一致
public function validateAuthKey($authKey)
{
    return $this->authKey === $authKey;
}
//判断密码是否一致
public function validatePassword($password)
{
    return $this->password === $password;
}

用户信息注册进认证组件源码

先从用户登录开始,控制器代码如下

class TestController extends Controller{
  public function actionD(){
      $user = Yii::$app->get("user");
      //使用安装yii后默认的models\User
      $identity = $user->identityClass::findIdentity(101);
      //将用户信息注册进用户认证
      $user->login($identity);
  }
}

login方法的源码如下

public function login(IdentityInterface $identity, $duration = 0)
{
    //执行beforeLogin事件,根据事件的isValid来判断是否可以登录
    if ($this->beforeLogin($identity, false, $duration)) {
        //将旧的用户认证信息从会话中删除,并且可选的是重新将authTimeoutParam、absoluteAuthTimeoutParam生存周期存进会话
        $this->switchIdentity($identity, $duration);
        //获取认证id
        $id = $identity->getId();
        //从request组件中获取客户端ip
        $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.";
        }
        //重新更新csrf
        $this->regenerateCsrfToken();
        //存日志
        Yii::info($log, __METHOD__);
        //执行登录后事件
        $this->afterLogin($identity, false, $duration);
    }
    //判断登录认证信息是否可用,也就是是否登录成功了
    return !$this->getIsGuest();
}

用户注册登录认证的时候,会有以下操作

  • 原有cookie中的认证信息删除(可选)
  • 更新session_id(可选)
  • 从session中删除id和expire属性
  • 将__id存进session,值是用户认证id
  • 重新从当前时间开始存authTimeoutParam和absoluteAuthTimeoutParam(可选)
  • 将认证信息存进cookie(可选)

    public function switchIdentity($identity, $duration = 0)
    {
      //存用户认证信息进类属性$this->_identity
      $this->setIdentity($identity);
      //如果不使用session则直接return
      if (!$this->enableSession) {
          return;
      }
      //是否将旧的cookie里面存的认证信息删除
      if ($this->enableAutoLogin && ($this->autoRenewCookie || $identity === null)) {
          $this->removeIdentityCookie();
      }
      //获取session组件
      $session = Yii::$app->getSession();
      //是否更新session id
      if (!YII_ENV_TEST) {
          $session->regenerateID(true);
      }
      //删除session中的值__id
      $session->remove($this->idParam);
      //删除session中的值__expire
      $session->remove($this->authTimeoutParam);
    
      if ($identity) {
          //设置会话信息__id为认证信息的id
          $session->set($this->idParam, $identity->getId());
          if ($this->authTimeout !== null) {
              //存__expire到session
              $session->set($this->authTimeoutParam, time() + $this->authTimeout);
          }
          if ($this->absoluteAuthTimeout !== null) {
              //存__absoluteExpire到session
              $session->set($this->absoluteAuthTimeoutParam, time() + $this->absoluteAuthTimeout);
          }
          //存cookie,key为_identity
          if ($this->enableAutoLogin && $duration > 0) {
              $this->sendIdentityCookie($identity, $duration);
          }
      }
    }
    

    也就是说如果使用enableAutoLogin并且cookie的周期设置大于0,会话中会有如下信息

  • session名字__expire,值为当前时间+authTimeout属性
  • session名字__absoluteExpire,值为当前时间+absoluteAuthTimeout属性
  • cookie名字为_identity,值为安全加密后的值
    cookie存储的具体源码如下,就是将用户认证的信息存入cookie,然后用底层的cookie加密
    protected function sendIdentityCookie($identity, $duration)
    {
      $cookie = Yii::createObject(array_merge($this->identityCookie, [
          'class' => 'yii\web\Cookie',
          'value' => json_encode([
              $identity->getId(),
              $identity->getAuthKey(),
              $duration,
          ], JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE),
          'expire' => time() + $duration,
      ]));
      Yii::$app->getResponse()->getCookies()->add($cookie);
    }
    

    这里要说一下会存两个session,值都是时间戳,具体区别如下

  • expire属性在登录后会判断当前时间戳是否小于__expire对应会话中的时间戳,小于则说明认证过期,大于则重新将当前时间戳+authTimeout存入会话
  • absoluteExpire属性在登录后会判断当前时间戳是否小于__expire对应会话中的时间戳,小于则说明认证过期

不注册直接从认证组件获取用户信息源码

控制器代码如下

class TestController extends Controller
{
	public function actionD(){
		$user = Yii::$app->get("user");
		var_dump($user->identity);
		return 123;
	}
}

会通过属性注入调用getIdentity方法

private $_identity = false;
public function getIdentity($autoRenew = true)
{
    if ($this->_identity === false) {
        if ($this->enableSession && $autoRenew) {
            try {
                $this->_identity = null;
                //从会话中获取认证信息
                $this->renewAuthStatus();
            } catch (\Exception $e) {
                $this->_identity = false;
                throw $e;
            } catch (\Throwable $e) {
                $this->_identity = false;
                throw $e;
            }
        } else {
            return null;
        }
    }

    return $this->_identity;
}
protected function renewAuthStatus()
{
    //获取session组件
    $session = Yii::$app->getSession();
    //如果cookie中有session_id或者已经session_start则从session中获取__id
    $id = $session->getHasSessionId() || $session->getIsActive() ? $session->get($this->idParam) : null;

    if ($id === null) {
        $identity = null;
    } else {
        /* @var $class IdentityInterface */
        $class = $this->identityClass;
        //通过会话中的认证id去查具体的认证信息
        $identity = $class::findIdentity($id);
    }
    ////存用户认证信息进类属性$this->_identity
    $this->setIdentity($identity);
    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;
        //当前时间戳是否小于__expire对应会话中的时间戳,小于则说明认证过期,大于则重新将当前时间戳+authTimeout存入会话
        //当前时间戳是否小于__expire对应会话中的时间戳,小于则说明认证过期
        if ($expire !== null && $expire < time() || $expireAbsolute !== null && $expireAbsolute < time()) {
            $this->logout(false);
        } elseif ($this->authTimeout !== null) {
            $session->set($this->authTimeoutParam, time() + $this->authTimeout);
        }
    }

    if ($this->enableAutoLogin) {
        if ($this->getIsGuest()) {
            //如果session中没有认证信息,就从cookie中查
            $this->loginByCookie();
        } elseif ($this->autoRenewCookie) {
            //延迟cookie生成周期
            $this->renewIdentityCookie();
        }
    }
}

如果选择了enableAutoLogin,那么如果从session中查不到认证信息就去查cookie,如果查到了认证信息就去验证cookie的生存周期

protected function renewIdentityCookie()
{
    $name = $this->identityCookie['name'];
    //获取一开始存进去的cookie认证信息
    $value = Yii::$app->getRequest()->getCookies()->getValue($name);
    if ($value !== null) {
        $data = json_decode($value, true);
        if (is_array($data) && isset($data[2])) {
            $cookie = Yii::createObject(array_merge($this->identityCookie, [
                'class' => 'yii\web\Cookie',
                'value' => $value,
                 //验证cookie生存周期
                'expire' => time() + (int) $data[2],
            ]));
            Yii::$app->getResponse()->getCookies()->add($cookie);
        }
    }
}
protected function loginByCookie()
{
    $data = $this->getIdentityAndDurationFromCookie();
    if (isset($data['identity'], $data['duration'])) {
        $identity = $data['identity'];
        $duration = $data['duration'];
        if ($this->beforeLogin($identity, true, $duration)) {
            $this->switchIdentity($identity, $this->autoRenewCookie ? $duration : 0);
            $id = $identity->getId();
            $ip = Yii::$app->getRequest()->getUserIP();
            Yii::info("User '$id' logged in from $ip via cookie.", __METHOD__);
            $this->afterLogin($identity, true, $duration);
        }
    }
}
protected function getIdentityAndDurationFromCookie()
{
    //获取一开始存入cookie的认证信息
    $value = Yii::$app->getRequest()->getCookies()->getValue($this->identityCookie['name']);
    if ($value === null) {
        return null;
    }
    $data = json_decode($value, true);
    if (is_array($data) && count($data) == 3) {
        list($id, $authKey, $duration) = $data;
        /* @var $class IdentityInterface */
        $class = $this->identityClass;
        //通过cookie中的认证id去查具体的认证信息
        $identity = $class::findIdentity($id);
        if ($identity !== null) {
            if (!$identity instanceof IdentityInterface) {
                throw new InvalidValueException("$class::findIdentity() must return an object implementing IdentityInterface.");
            } elseif (!$identity->validateAuthKey($authKey)) {
                //还会验证cookie中的AuthKey
                Yii::warning("Invalid auth key attempted for user '$id': $authKey", __METHOD__);
            } else {
                return ['identity' => $identity, 'duration' => $duration];
            }
        }
    }
    //删除本次cookie信息
    $this->removeIdentityCookie();
    return null;
}
觉得很赞
    没有找到数据。
您需要登录后才可以评论。登录 | 立即注册