用户认证类 Authentication 源码剖析 [ 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;
}
Zhucola China
最后登录:2019-10-12
在线时长:11小时36分
- 粉丝11
- 金钱800
- 威望100
- 积分1910
热门源码
- 基于 Yii 2 + Bootstrap 3 搭建一套后台管理系统 CMF
- 整合完 yii2-rbac+yii2-admin+adminlte 等库的基础开发后台源码
- 适合初学者学习的一款通用的管理后台
- yii-goaop - 将 goaop 集成到 Yii,在 Yii 中优雅的面向切面编程
- yii-log-target - 监控系统异常且多渠道发送异常信息通知
- 店滴云1.3.0
- 面向对象的一小步:添加 ActiveRecord 的 Scope 功能
- Yii2 开源商城 FecShop
- 基于 Yii2 开发的多店铺商城系统,免费开源 + 适合二开
- leadshop - 基于 Yii2 开发的一款免费开源且支持商业使用的商城管理系统
共 0 条评论