我正在尝试阻止从其他设备登录。经过所有研究后我发现使用会话。我使用的是 yii2 框架的默认登录系统。并将这些代码添加到
user
模型中。
用户模型是:
<?php
namespace app\models;
use Yii;
//app\models\Users is the model generated using Gii from users table
use app\models\Users as DbUser;
class User extends \yii\base\Object implements \yii\web\IdentityInterface {
public $id;
public $username;
public $password;
public $authKey;
public $token;
public $email;
public $any;
public $user_type;
/**
* @inheritdoc
*/
public static function findIdentity($id) {
$dbUser = DbUser::find()
->where([
"id" => $id
])
->one();
if (!count($dbUser)) {
return null;
}
return new static($dbUser);
}
/**
* @inheritdoc
*/
public static function findIdentityByAccessToken($token, $userType = null) {
$dbUser = DbUser::find()
->where(["token" => $token])
->one();
if (!count($dbUser)) {
return null;
}
return new static($dbUser);
}
/**
* Finds user by username
*
* @param string $username
* @return static|null
*/
public static function findByUsername($username) {
$dbUser = DbUser::find()
->where([
"username" => $username
])
->one();
if (!count($dbUser))
{
return null;
}
return new static($dbUser);
}
/**
* @inheritdoc
*/
public function getId() {
return $this->id;
}
/**
* @inheritdoc
*/
public function getAuthKey()
{
return $this->authKey;
}
/**
* @inheritdoc
*/
public function validateAuthKey($authKey)
{
return $this->authKey === $authKey;
}
/**
* Validates password
*
* @param string $password password to validate
* @return boolean if password provided is valid for current user
*/
public function validatePassword($password)
{
return Yii::$app->getSecurity()->validatePassword($password, $this->password);
}
public function session_validate()
{
// Encrypt information about this session
$user_agent = $this->session_hash_string($_SERVER['HTTP_USER_AGENT'], $this->any);
// Check for instance of session
if ( session_exists() == false )
{
// The session does not exist, create it
$this->session_reset($user_agent);
}
// Match the hashed key in session against the new hashed string
if ( $this->session_match($user_agent) )
{
return true;
}
// The hashed string is different, reset session
$this->session_reset($user_agent);
return false;
}
/**
* session_exists()
* Will check if the needed session keys exists.
*
* @return {boolean} True if keys exists, else false
*/
private function session_exists()
{
return isset($_SESSION['USER_AGENT_KEY']) && isset($_SESSION['INIT']);
}
/**
* session_match()
* Compares the session secret with the current generated secret.
*
* @param {String} $user_agent The encrypted key
*/
private function session_match( $user_agent )
{
// Validate the agent and initiated
return $_SESSION['USER_AGENT_KEY'] == $user_agent && $_SESSION['INIT'] == true;
}
/**
* session_encrypt()
* Generates a unique encrypted string
*
* @param {String} $user_agent The http_user_agent constant
* @param {String} $unique_string Something unique for the user (email, etc)
*/
private function session_hash_string( $user_agent, $unique_string )
{
return md5($user_agent.$unique_string);
}
/**
* session_reset()
* Will regenerate the session_id (the local file) and build a new
* secret for the user.
*
* @param {String} $user_agent
*/
private function session_reset( $user_agent )
{
// Create new id
session_regenerate_id(TRUE);
$_SESSION = array();
$_SESSION['INIT'] = true;
// Set hashed http user agent
$_SESSION['USER_AGENT_KEY'] = $user_agent;
}
/**
* Destroys the session
*/
private function session_destroy()
{
// Destroy session
session_destroy();
}
}
下面是更好的方法。
您要做的就是在用户表中添加一个额外的列,将其命名为“conc_login”或最好是文本,因为可能很难预测我们期望的数据大小。
当用户登录时,检查logins列是否为空,如果为空,则使用time()函数创建一个包含session_id、登录时间的json。
如果登录数列不为空或者解码时该列的计数大于零,则检查计数是否大于登录限制,如果登录数尚未大于登录限制,则追加新会话到数据库表中的登录列
如果达到登录限制,则检查登录记录并检查是否有过期的会话,例如,假设用户在 300 秒内不活动,则认为该用户已注销,则从登录中删除已过期的会话表
在登录用户发出的任何请求中,您检查会话密钥是否仍然存在于数据库的登录列中($logins['session_key']),如果没有找到,则立即注销用户以避免权利升级,否则将 $login['time'] 更新为新的 time()。
您可以实现此代码。
登录表单模型添加此功能以进行并发用户验证
$get_limit = 设置::findOne(['name' => 'login_limit']);
$login_limit = 3; //$get_limit->值;
$active_sess = User::findOne($getUser->id);
if ($active_sess->conc_login == '' 或 count(json_decode($active_sess->conc_login)) == 0) {
$login_json = json_encode([
[Yii::$app->session->getId() => Yii::$app->session->getId(), 'session_key' => Yii::$app->session->getId(), '时间' => 时间()]
]);
$active_sess->conc_login = $login_json;
$active_sess->save();
} else if (count(json_decode($active_sess->conc_login)) > 0 and count(json_decode($active_sess->conc_login)) $login_json = json_decode($active_sess->conc_login);
$login_json[] = [Yii::$app->session->getId() => Yii::$app->session->getId(), 'session_key' => Yii::$app->session->getId(), 'time' => time()];
$login_json = json_encode($login_json);
//print_r($login_json); exit;
$active_sess->conc_login = $login_json;
$active_sess->save();
} else if (count(json_decode($active_sess->conc_login)) >= $login_limit) {
$logins = json_decode($active_sess->conc_login);
foreach ($logins as $key => $login) {
if ($login->time < time() - 120) {
//this checks if the iterated login is greater than the current time -120seconds and if found to be true then the user is inactive
//then set this current login to null by using the below statement
//$logins[$key] = null; // or unset($logins[$key]) either should work;
unset($logins[$key]);
}
}
//after iteration we check if the count of logins is still greater than the limit
if (count($logins) >= $login_limit) {
//then return a login error that maximum logins reached
//echo 'you are not allowed to login as you have breeched the maximum session limit.';
//exit;
$login_json = json_encode($logins);
$active_sess->conc_login = $login_json;
$active_sess->save();
$this->addError($attribute, 'you are not allowed to login as you have breeched the maximum session limit.');
} else {
//then login is successsfull
$login_json = [];
foreach ($logins as $key => $val) {
$login_json[] = [$val->session_key => $val->session_key, 'session_key' => $val->session_key, 'time' => $val->time];
}
$login_json[] = [Yii::$app->session->getId() => Yii::$app->session->getId(), 'session_key' => Yii::$app->session->getId(), 'time' => time()];
$login_json = json_encode($login_json);
$active_sess->conc_login = $login_json;
$active_sess->save();
}
//update the logins column to equal to json_encode($logins);
}
用户端每 60 秒更新一次会话:SiteController
公共函数actionUserSessionUpdate() { $session = Yii::$app->session; $userid = $session->get('userid'); $username = $session->get('用户名'); $data = array('session_id' => Yii::$app->session->getId()); $isUserLogin = (!empty($userid) && !empty($username)) ? '真假'; 如果($isUserLogin == '假'){ 回声'gotologin';出口; //返回$this->redirect(['/login']); } 别的 { //登录用户}$active_sess = Clientmanagement::findOne($userid); $loginjson = json_decode($active_sess->conc_login); $login_json = []; foreach ($loginjson as $key => $val) { if ($val->session_key == Yii::$app->session->getId()) { $login_json[] = [$val->session_key => $val->session_key, 'session_key' => $val->session_key, 'time' => time()]; } else { $login_json[] = [$val->session_key => $val->session_key, 'session_key' => $val->session_key, 'time' => $val->time]; } } $login_json = json_encode($login_json); $active_sess->conc_login = $login_json; $active_sess->save(); } exit;
客户端ajax
<?php
if (!empty($session->get('userid'))) {
$usersession_url = Yii::$app->urlManager->createAbsoluteUrl(["/site/user-session-update"]);
$scriptb = <<< JS
$(document).ready(function () {
//Wait 1 Minutes
setInterval(function(){
$.ajax({
type:"post",
data:'id=session',
url:"$usersession_url",
success:function(data){
}
});
}, 60000);
});
JS;
$this->registerJs($scriptb);
}
?>
用户控制器注销
public function actionLogout() {
$session = Yii::$app->session;
$userid = $session->get('userid');
//concurrent active user session removed
$active_sess = Clientmanagement::findOne($userid);
$loginjson = json_decode($active_sess->conc_login);
foreach ($loginjson as $key => $login) {
if ($login->session_key == Yii::$app->session->getId()) {
unset($loginjson[$key]);
}
}
$login_json = json_encode($loginjson);
$active_sess->conc_login = $login_json;
$active_sess->save();
$session->destroy();
return $this->redirect(['/login']);
}
我们可以在组件中设置该代码,它也适用于每个控制器和操作。另外,我更改了代码
公共/组件/LoginDevicesLimit.php
<?php
namespace app\common\components;
use Yii;
/**
* Logged In Devices Storage Helper
*/
class LoginDevicesLimit extends \yii\base\Component
{
public function init() {
$this->saveLoginSession();
parent::init();
}
public function saveLoginSession(){
$login_limit = env('DEVICES_LIMIT', 3);
$active_sess = \Yii::$app->user->identityClass::findOne(\Yii::$app->user->id);
if(!$active_sess){
return false;
}
$session_count = count(json_decode($active_sess->conc_login, true));
if ($active_sess->conc_login == '' || $session_count == 0) {
$login_json = json_encode([
Yii::$app->session->getId() => ['session_key' => Yii::$app->session->getId(), 'time' => time()]
]);
$active_sess->conc_login = $login_json;
$active_sess->save();
} else if ($session_count > 0 && $session_count < $login_limit) {
$login_json = json_decode($active_sess->conc_login, true);
$login_json[Yii::$app->session->getId()] = ['session_key' => Yii::$app->session->getId(), 'time' => time()];
$login_json = json_encode($login_json);
//print_r($login_json); exit;
$active_sess->conc_login = $login_json;
$active_sess->save();
} else if ($session_count > $login_limit) {
$logins = json_decode($active_sess->conc_login, true);
foreach ($logins as $key => $login) {
if ($login->time < time() - 120) {
//this checks if the iterated login is greater than the current time -120seconds and if found to be true then the user is inactive
//then set this current login to null by using the below statement
//$logins[$key] = null; // or unset($logins[$key]) either should work;
unset($logins[$key]);
}
}
//after iteration we check if the count of logins is still greater than the limit
if (count($logins) > $login_limit) {
//then return a login error that maximum logins reached
//echo 'you are not allowed to login as you have breeched the maximum session limit.';
//exit;
$login_json = json_encode($logins);
$active_sess->conc_login = $login_json;
$active_sess->save();
throw new \yii\web\HttpException(404, 'You are not allowed to login as you have reached the maximum session limit.');
} else {
//then login is successsfull
$login_json = [];
foreach ($logins as $key => $val) {
$login_json[] = [$val->session_key => $val->session_key, 'session_key' => $val->session_key, 'time' => $val->time];
}
$login_json[] = [Yii::$app->session->getId() => Yii::$app->session->getId(), 'session_key' => Yii::$app->session->getId(), 'time' => time()];
$login_json = json_encode($login_json);
$active_sess->conc_login = $login_json;
$active_sess->save();
}
}
}
}
您可以在登录过程中生成额外的身份验证代码,该代码将存储在用户身份中并保存在数据库中。每次用户调用操作时,他的代码都会与数据库中保存的代码进行比较,如果不匹配,用户将被强制注销。这样只有最新的登录才有效。参考这个链接
这是防止从其他设备登录的另一种方法。
您需要使用 webvimark/user-management 库配置您的应用程序,如下所示。
CREATE TABLE `user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(255) NOT NULL,
`auth_key` varchar(32) NOT NULL,
`password_hash` varchar(255) NOT NULL,
`confirmation_token` varchar(255) DEFAULT NULL,
`status` int(11) NOT NULL DEFAULT '1',
`superadmin` smallint(1) DEFAULT '0',
`created_at` int(11) NOT NULL,
`updated_at` int(11) NOT NULL,
`registration_ip` varchar(15) DEFAULT NULL,
`bind_to_ip` varchar(255) DEFAULT NULL,
`email` varchar(128) DEFAULT NULL,
`email_confirmed` smallint(1) NOT NULL DEFAULT '0',
`sessions` longtext,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8
<?php
$config =
[
'bootstrap' => ['log', 'LogoutDevices'],
'components' =>
[
'user' => ['class' => 'webvimark\modules\UserManagement\components\UserConfig', 'enableAutoLogin' => true, 'on afterLogin' => function($event) {(new \app\components\ABCDEFG\DevicesLimit\LoginDevicesLimit())->sessionManager(); \webvimark\modules\UserManagement\models\UserVisitLog::newVisitor($event->identity->id);}],
'LogoutDevices' => ['class' => 'app\components\ABCDEFG\DevicesLimit\LogoutDevices']
]
]
然后,你需要使用以下类:
<?php
namespace app\models\UserManagement;
use Yii;
/**
* This is the model class for table "user".
*
* @property string $sessions
*/
class User extends \webvimark\modules\UserManagement\models\User
{
function __construct($config = [])
{
parent::__construct($config);
}
function init()
{
parent::init();
}
}
<?php
namespace app\components\ABCDEFG\DevicesLimit;
use Yii;
use yii\base\Component;
use app\models\UserManagement\User;
/**
* Logged In Devices Storage Helper
*
* Class LoginDevicesLimit
* @package app\components\ABCDEFG\DevicesLimit
*
* @property integer $iDevicesLimit
*
*/
class LoginDevicesLimit extends Component
{
private int $iDevicesLimit = 1;
public function init()
{
parent::init();
}
public function getDevicesLimit() : int
{
return $this->iDevicesLimit;
}
public function setDevicesLimit(int $iDevicesLimit = 1) : self
{
if($iDevicesLimit >= 1)
{
$this->iDevicesLimit = $iDevicesLimit;
}
return $this;
}
public function sessionManager() : self
{
if(isset(Yii::$app->user->id))
{
$oUser = User::findOne(Yii::$app->user->id);
if(!empty($oUser))
{
if(User::hasRole('Expert', $superAdminAllowed = false))
{
$this->setDevicesLimit(3);
}
$sSessionsJSON = $oUser->sessions;
$aSessions = json_decode($sSessionsJSON, true);
$aSession = Helper::getCurrentSessionData();
if(is_array($aSessions) && !empty($aSessions))
{
$bIsSessionExists = false;
foreach($aSessions as $iSessionKey => $aSessionData)
{
if($aSessionData['id'] == $aSession['id'])
{
$aSessions[$iSessionKey] = $aSession;
$bIsSessionExists = true;
break;
}
}
if($bIsSessionExists == true)
{
$aTime = array_column($aSessions, 'time');
array_multisort
(
$aTime, SORT_NUMERIC, SORT_ASC,
$aSessions
);
}
else
{
array_unshift($aSessions, $aSession);
}
}
else
{
$aSessions[] = $aSession;
}
$aSessions = array_slice($aSessions, 0, $this->getDevicesLimit());
$sSessionsJSON = json_encode($aSessions);
if(json_last_error() == JSON_ERROR_NONE)
{
$oUser->sessions = $sSessionsJSON;
$oUser->save();
}
}
}
return $this;
}
}
<?php
namespace app\components\ABCDEFG\DevicesLimit;
use Yii;
use yii\base\Component;
use app\models\UserManagement\User;
/**
* Logged In Devices Storage Helper
*
* Class LogoutDevices
* @package app\components\ABCDEFG\DevicesLimit
*
*/
class LogoutDevices extends Component
{
public function init()
{
parent::init();
self::logoutOnExceedingLimit();
}
public static function logoutOnExceedingLimit()
{
$xRetValue = NULL;
if(Yii::$app->request->isAjax == false && isset(Yii::$app->user->id))
{
$oUser = User::findOne(Yii::$app->user->id);
if(!empty($oUser))
{
$sSessionsJSON = $oUser->sessions;
$aSessions = json_decode($sSessionsJSON, true);
$aSession = Helper::getCurrentSessionData();
if(is_array($aSessions) && !empty($aSessions))
{
$bIsSessionExists = in_array($aSession['id'], array_column($aSessions, 'id'));
if($bIsSessionExists == false)
{
Yii::$app->session->setFlash('devices-limit', true);
Yii::$app->session->close();
Yii::$app->user->logout(false);
$xRetValue = Yii::$app->response->redirect(['site/devices-limit-reached'], 302);
}
}
}
}
return $xRetValue;
}
}
<?php
namespace app\components\ABCDEFG\DevicesLimit;
use Yii;
use yii\base\Component;
/**
* Helper
*
* Class Helper
* @package app\components\ABCDEFG\DevicesLimit
*
*/
class Helper extends Component
{
public function init()
{
parent::init();
}
public static function getCurrentSessionData() : array
{
return
[
'id' => Yii::$app->session->getId(),
'time' => time(),
'device' => Yii::$app->request->userAgent,
];
}
}