检查 sessionid 已知的 PHP 会话是不是处于活动状态

Posted

技术标签:

【中文标题】检查 sessionid 已知的 PHP 会话是不是处于活动状态【英文标题】:check if a PHP session known by the sessionid is active检查 sessionid 已知的 PHP 会话是否处于活动状态 【发布时间】:2017-07-06 10:55:27 【问题描述】:

我们正在为客户锁定一些库存,锁定库存的表包含锁定它的人的会话 ID 以及有关客户端的其他信息。当会话到期时,我们希望解锁该库存,以便其他人可以购买它。因为我们在表中注册了 session_id(),知道了,有没有办法在 php 中检查 session 是否仍然处于活动状态?

如果我们使用数据库来保持会话,我们可能会检查该行是否直到那里以及最后一个活动是什么时候,在 memcached 中我们可能会找出会话的键并像这样检查它,因为文件会话我们可能也可以这样做,找出会话的文件名并检查文件是否存在。

无论您在哪里进行会话,有什么东西可以在任何地方都有效吗?

【问题讨论】:

为什么不能使用数据库? 我不是问这种事情的人,但我会从这个开始,然后从那里开始。if(session_status() === PHP_SESSION_ACTIVE)。我的意思是,如果这不起作用,我将不得不深入挖掘。 :-) 这可能有助于解决问题的第一部分。还有if(session_status() === PHP_SESSION_NONE) 【参考方案1】:

总之,您是在询问 PHP 中是否有通用方法来访问任何会话。尽管问题中的用例是合理的,但能够按照您的建议进行操作的影响会带来巨大的安全风险。

这就是为什么 PHP 中的内置会话功能使您难以完成所需工作的原因之一。

理论上您可以使用inbuilt PHP session functions 指定特定的会话ID 并查找它们。我刚刚做了一些简单的测试,并没有取得太大的成功。只有一个用于加载会话“session_start”的内置函数需要重复调​​用。该手册明确表示这不起作用:

从 PHP 4.3.3 开始,在会话之前启动之后调用 session_start() 将导致 E_NOTICE 级别的错误。此外,第二个会话开始将被忽略。

也许仍然可以通过分叉或其他巧妙的方法来解决这个问题。但是您的代码将以一种晦涩难懂的方式运行,这可能会与未来的 PHP 更新中断或可能干扰现有的实时会话。

最好的解决方案是编写一个特定于正在使用的会话处理程序的工具,该工具允许对会话进行只读访问。问题中的场景甚至不需要访问会话数据,只需要可用于计算到期时间的时间戳信息。

在“文件”会话处理的情况下。可以使用ini_get('session.save_path'); 发现会话文件存储的路径。检查脚本可能需要以与 Web 服务器相同的权限运行才能访问此位置。

脚本需要按计划频繁运行,以检查过期会话并从清单中移除锁定。

【讨论】:

我同意,我正在尝试做的事情感觉不对:)。之后我会觉得很脏。我们现在所做的是将 memcached 用于会话和缓存。因为 Laravel 使用 session ID 作为 memcached 的 key,我相信我们可以检查 key 是否存在,然后确定 session 是否仍然处于活动状态。 应该可以。 memcached 会话密钥(应该是为了防止冲突)不仅仅是会话 ID。 Dump the keys 找出您正在寻找的模式。 从我的测试来看,会话密钥与 memcached 密钥相同。它应该很容易工作。【参考方案2】:

您实际上可以为此使用session_idsession_start

$ids = [
            '135b29ef958a23418f2a804474787305', // active session
            '135b29ef958a23418f2a804474787306', // inactive session
            '135b29ef958a23418f2a804474787305', // active session
        ];

foreach($ids as $id)

    session_id($id);
    session_start(); 

    $status = isset($_SESSION['logged_in']); 

    print( ($status ? 1 : 0) . PHP_EOL);
    session_abort();

检查是否存在始终设置的会话变量。确保这不是新创建的会话。

您必须检查这是否不会重置会话上的生命周期计数器。在我的系统上,它不会影响生命周期,直到会话发生变化

编辑:更新为session_abort 以循环和检查多个会话ID

【讨论】:

谢谢,我不知道 session_abort()。您确定当您打开会话时它不会延长吗?我不想让会话永远延长。 @Sander Backus。我很好奇,你测试过这个吗?当我尝试它时它没有工作,我非常想知道是否有办法让它工作。 我确实对此进行了测试。正如@shukshin.ivan 提到的,您必须确保该进程具有读取会话的权限(例如,通过您的网络服务器运行它,而不是在 cli 中)。在我的系统上,会话文件的时间戳不会改变。使用自定义会话处理程序,时间戳也不应该改变。我不确定这是如何处理的,例如 memcached。也许您可以在运行此之前 ini_set session.gc_probability, session.gc_divisor 以确保在检查会话是否存在之前运行垃圾收集?然后在超时后检查每个会话以确保它们不会延长 我的 debian 更改了会话对文件的访问时间。【参考方案3】:

您不能使用session_idsession_start 等。

这些函数处理请求页面的用户会话。您不能使用它们轻松处理所有用户会话的列表。您必须担心用户创建的会话 (www-data) 和 cli 脚本的相同权限,这将被执行以查找应该解锁的 - 会话的文件仅供所有者使用。

此外,会话的访问时间已更改,因此每次检查会话都会更改时间并影响会话的行为。

如何更轻松地执行锁定

通常会在固定的时间内锁定货物,例如 20 分钟。但是,如果您希望在会话处于活动状态时保持选中的库存,您可以对现有代码稍作修改。

    session_id 字段旁边添加一个新字段last_activity(一个简单的迁移) 每个请求都将其更新为 NOW(只需在模型上调用 $row->touch() - docs)。 创建一个 crontab 任务以删除带有last_activity < NOW - delta 的每一行,其中delta 是会话超时。

使用这种方法,您可以避免处理每个会话驱动程序。

附:您可能希望确保会话在一定时间后终止,read this question。如果这项工作由垃圾收集器完成,会话生命周期可能会有所不同。

【讨论】:

我知道垃圾收集器,我不应该指望它。我试图说服企业它应该被锁定一段时间,而不是在用户登录时,但是这是在我上船之前向客户承诺的,所以我们只需要这样做。建议会议的方式正是我想要实施的方式,但有人告诉我我们有承诺。 @MihaiP。如果您在用户登录并处于活动状态时延长“锁定”时间怎么办?它将符合业务的要求。 不过您需要配置会话。如果用户不打开任何页面,您不能允许他们永久锁定项目。 “当用户登录时”是什么意思?如果他选中“记住我”,他可以永远锁定项目吗? “记住我”会在您访问网站时重新登录,它不会以任何方式延长您的会话。另一个解决方案是在每次页面加载时更新记录时间并在一段时间后删除所有记录(比如说 40 分钟)。这需要在每次页面加载时进行更新,我试图尽可能少地调用数据库。是的,根据业务,当用户活跃在网站上时,库存会被锁定。 is locked while the user is actively on the website 是什么意思?你必须定义它。如果这意味着在会话超时时解锁 - 我的解决方案适合它。 logs you back - 对,但你确定那个商人知道吗?【参考方案4】:

检查会话文件夹中是否存在会话:

echo ini_get('session.save_path');

例如在我的文件夹会话 id 文件中:

sess_l75lm4ktlhvlu566n8i6do8k71

设置会话参数(例如):

ini_set('session.cookie_lifetime', 100)

在 session_destroy() 之后;文件已从会话文件夹中删除:

        if (file_exists($sessionfile)) 
        echo "exists";
    else
        echo "not exist"
    

或者您可以在关闭浏览器和解锁产品时发送js请求(firefox测试):

    <script type="text/javascript">
    // alert(document.cookie);

    window.onbeforeunload = function () 
    return "Do you really want to close?";
    ;
    window.onunload = function () 
    return "Do you really want to close?";
    ;

    $(window).on('beforeunload', function() alert ('Send post to unlock product'));
    $(window).unload( function ()  alert("'Send post to unlock product'");  );
</script>

【讨论】:

这是一个解决方案,是的,但这仅考虑了文件会话。会话可以在任何地方(数据库、文件、memcached)我希望有一个通用的解决方案。我知道如果会话在数据库或内存缓存中,我可以做类似的事情。 PHP 将会话信息写入 session.save_path 中指定的所有服务器;类似于 RAID-1。您需要检查会话 cookie。 或者从 PHP 向会话 cookie 添加 id(锁定项),然后所有用户都知道 或在关闭浏览器(选项卡)时删除会话ID(来自javascript事件)【参考方案5】:

您可以将代码放入注销用户的函数中,该函数应处理与该会话相关的库存解锁。

【讨论】:

您的建议无法处理关闭浏览器窗口。之后我仍然需要解锁它。 处理这种情况请参考:***.com/questions/13443503/… 此外,会话超时应处理注销使用。 我过去曾这样做过,它相当有问题,而且你仍然指望浏览器提供一些东西。如果互联网出现故障,用户关闭笔记本电脑,浏览器打开,他们在手机上,他们就忘记了,等等。 会话超时【参考方案6】:

关闭浏览器窗口会触发事件beforeunload,该事件只能由 Javascript 处理。您可以为该事件添加一个 Javascript 事件处理程序,该事件处理程序会向该站点发出 AJAX 回调,如下所示(给出的 jQuery 示例):

$(window).bind("beforeunload", function()  
    $.ajax(
        url: "http://example.com/destroySession.php"
    );
);

该站点的回调可能针对一个页面,该页面根据用户的 sessionID 解锁库存项目,之后调用 PHP 命令session_destroy()。例如:

<?php

// Load your config bootstrap / etc.

session_start();

// Unlock target inventory items

session_destroy();

?>

【讨论】:

那么如果用户保持浏览器打开,该项目将永远不会被解锁? 不一定,会话很可能有超时。 OP 没有提供处理会话插入/检查数据库表的代码。没有这些信息,我只能假设。【参考方案7】:

您可以做的是在每个用户会话中保留一个标志(例如 last_activity),以检查您的会话是否处于活动状态并确定是解锁项目还是保持锁定状态。 这对我有用:

$sessionId = get_session_id_that_locked_the_item_from_db();

if(session_status() !== PHP_SESSION_ACTIVE) 
    session_start();

// get current session id.
$current_id = session_id();
// renew activity.
echo 'Renewed my activity.';
$_SESSION['active'] = 1;
// Close the current session
session_write_close();

if($current_id === $sessionId) 
    // the current user has locked the item.

// Check if the var active of this session exists.
elseif(!checkSessionlastActivity($sessionId, $current_id)) 
    // Show the inventory item.
    showInventoryItem();
    // Lock inventory when the item is selected(eg added to basket).
    if('I want to lock the item') 
       lockInventoryItem($current_id);
    

else 
    echo 'Item is locked.';


function checkSessionlastActivity($id, $current_id) 
    // switch session to the one that locked the item.
    session_id($id);
    session_start();
    // get session is active.
    $active = ( isset($_SESSION['active'])? $_SESSION['active']: null);
    // Close the session.
    session_abort();
    // restore current session.
    session_id($current_id);
    session_start();

    if($active !== null) 
        return true;
    
    return false;


function lockInventoryItem($current_id) 
    put_my_session_id_to_db($current_id);


function showInventoryItem() 
    echo 'You can select item';

注意:我不确定这是否适用于不同的系统。这可能取决于您的 php 会话设置。

意见:会话用于特定功能。我认为在属于不同用户的会话之间切换并不是会话的设计目的。无论如何,我建议您在为库存实施锁定解决方案之前使用此解决方法。

如果您只依赖会话是否已过期,请检查您的会话设置(session.gc_maxlifetime、session.gc_probability 和 session.gc_divisor),这也可以helpful。

【讨论】:

如果我启动会话来检查它是否处于活动状态,这不会无限期地延长会话吗? 是的,实际上它会更新会话的时间戳,但在我的解决方案中,您不依赖于会话的更新时间戳,而是“last_activity”。如果您关心这一点,请使用 session_abort() 而不是 session_write_close() 进行数据库会话。我将更新我的答案,您还需要考虑一些事项。【参考方案8】:
// correct way
$id = 'abc';
session_id($id);
session_start();
$isActive = (session_status() === PHP_SESSION_ACTIVE);
session_write_close();
var_dump($isActive);

// tricky way (root required)
$id = 'abc';
$isActive = false;
$sessionTimeout = (int)ini_get('session.gc_maxlifetime');
$rdi = new RecursiveDirectoryIterator(session_save_path(), FilesystemIterator::SKIP_DOTS);
$rii = new RecursiveIteratorIterator($rdi);
$ri = new RegexIterator($rii, preg_quote("/sess_$id/"), RegexIterator::MATCH);
/**
 * @var $fileInfo SplFileInfo
 */
foreach ($ri as $fileInfo) 
    $expiredAt = $fileInfo->getMTime() + $sessionTimeout;
    $isActive = $expiredAt > time();
    if ($isActive) 
        break;
     

var_dump($isActive);

通常,将会话 ID 存储在 DB 中,而不是将会话保留在那里 - 这是一个坏主意。

看看http://php.net/manual/en/function.session-set-save-handler.php

根据您的个人资料,您知道 yii。 有一个很好的实现示例:

https://github.com/yiisoft/yii2/blob/master/framework/web/DbSession.php

https://github.com/yiisoft/yii2/blob/master/framework/web/Session.php#L97

https://github.com/yiisoft/yii2/blob/master/framework/web/Session.php#L148

【讨论】:

以上是关于检查 sessionid 已知的 PHP 会话是不是处于活动状态的主要内容,如果未能解决你的问题,请参考以下文章

检查 PHP 会话是不是已经开始

如何检查 PHP 会话是不是为空? [复制]

使用 AJAX 检查 PHP 会话是不是存在或过期

php 会话控制(关于session的维护与生命周期)

HTTP Session 理解

如何在 Codeigniter 中检查是不是设置了会话?