yii2框架中防止多设备登录

问题描述 投票:0回答:4

我正在尝试阻止从其他设备登录。经过所有研究后我发现使用会话。我使用的是 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();
    }

}
php authentication security yii2
4个回答
1
投票

下面是更好的方法。

  1. 您要做的就是在用户表中添加一个额外的列,将其命名为“conc_login”或最好是文本,因为可能很难预测我们期望的数据大小。

  2. 当用户登录时,检查logins列是否为空,如果为空,则使用time()函数创建一个包含session_id、登录时间的json。

  3. 如果登录数列不为空或者解码时该列的计数大于零,则检查计数是否大于登录限制,如果登录数尚未大于登录限制,则追加新会话到数据库表中的登录列

  4. 如果达到登录限制,则检查登录记录并检查是否有过期的会话,例如,假设用户在 300 秒内不活动,则认为该用户已注销,则从登录中删除已过期的会话表

  5. 在登录用户发出的任何请求中,您检查会话密钥是否仍然存在于数据库的登录列中($logins['session_key']),如果没有找到,则立即注销用户以避免权利升级,否则将 $login['time'] 更新为新的 time()。

您可以实现此代码。

  1. 登录表单模型添加此功能以进行并发用户验证

    
    $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);
    
    }
  2. 用户端每 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;
    
    }
  3. 客户端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);
    }
    ?>
    
  4. 用户控制器注销

    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']);
    }
    

1
投票

我们可以在组件中设置该代码,它也适用于每个控制器和操作。另外,我更改了代码

公共/组件/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();
            }
        }
    }
}

0
投票

您可以在登录过程中生成额外的身份验证代码,该代码将存储在用户身份中并保存在数据库中。每次用户调用操作时,他的代码都会与数据库中保存的代码进行比较,如果不匹配,用户将被强制注销。这样只有最新的登录才有效。参考这个链接


0
投票

这是防止从其他设备登录的另一种方法。

您需要使用 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,
            ];
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.