更新另一个用户的会话-最佳实践?

4
在许多Web应用程序中,当用户登录我的网站时,我设置了几个会话变量来控制访问站点功能的权限。每当用户执行会导致这些权限更改的操作时,我都会更新会话变量作为正常处理流程的一部分。
但有时候需要根据其他用户的操作更新用户会话。例如,管理员升级用户的权限。管理员无法访问用户的会话空间,因此无法为受影响的用户运行正常的更新功能。
我考虑过几种强制更新其他用户会话的方法,但它们都有缺点:
- 我可以搜索并从会话表中删除其会话,但这需要进行全表扫描(其中sessdata类似于'%user_id%'),并且可能会导致受影响用户正在生成的内容丢失。 - 我可以定期强制进行会话更新,例如在触发sess_time_to_update时。但不能保证在用户尝试访问需要更新的功能之前会发生这种情况。 - 我可以在每个页面加载时运行完整的变量更新系列,但维护会话的全部意义在于避免这种开销。 - 我可以设置一个指示需要会话更新的标志,但该信号必须在每个页面执行和每个控制器中进行查询。(我知道可以将CI_Controller扩展为MY_Controller,但我不想这样做) - 我已经向用户发送了一封电子邮件,通知他们权限的更改,让他们轻松点击会话更新链接(甚至注销并重新登录)。但无法保证他们会阅读主题行之外的内容,更不用说实际点击链接了。
那么我是否遗漏了什么?我是否在寻找不存在的神奇解决方案?
(请注意,我已经标记了CodeIgniter,并引用了CI特定的技术细节,因为我正在使用它。也就是说,这不是一个CI特定(甚至不是PHP特定)的问题。)
感谢您的帮助!

你的用户数据是否在一个用户对象模型中?如果是,你可以编写一个静态刷新方法,在任何需要更新权限的操作之前直接在同一请求中运行。 - DeaconDesperado
4个回答

3

好的,一种选择是设置一个“ACL版本”号(为所有用户或每个用户都设置),然后在初始化会话时(也就是调用session_start()时)检查存储的版本是否与会话的版本匹配。如果不匹配,则刷新ACL。

另一种稍微不同的方式是向会话表添加一列(例如“状态”或“脏”等)。然后,不要终止会话,只需将该状态更新为1。然后在加载会话时,检查标志以查看它是否为1。如果是,则重新加载会话中的缓存数据并将其设置为0。如果没有,则继续进行...

至于更新另一个用户的会话,我不会这样做...如果会话由PHP管理,那么您需要终止当前会话并启动要修改的会话。这只会导致问题...(因为PHP的机制不使用序列化)。

至于删除他们的会话,我不会担心全表扫描。除非您有大量用户经常更新权限,否则这并不重要。数据丢失是一个重要问题,所以取消了这个想法...

至于其他两个选项,我认为您已经说得很到位了,所以我没有其他建议...


我不必担心PHP机制/限制,因为我正在使用CI的会话功能。(我也没有ACL。相反,我让每个控制器根据会话设置来确定什么是适当的。我需要认真考虑重构以包括ACL)话虽如此,考虑到这将很少需要,我开始认为你对FTS并不是那么重要。但是,我应该更新相关变量,而不是删除受影响用户的会话。 - coolgeek
实际上,我的访问控制比通常使用通用 ACL 处理的细粒度更好。例如,我的用户可能被允许发布到某些博客,但永远不会被赋予发布到所有博客的权限。并且大部分权限授予是自动完成的,无需管理员干预,因此会话更新不是问题。我将继续思考这个问题,但最重要的是,我的访问控制与传统的 ACL 模型(例如 Drupal)不匹配。 - coolgeek
@Coolgeek:我对那条评论感到困惑。您没有在会话中缓存ACR(访问控制规则)吗?如果没有,那么提出这个问题有什么意义(因为下一个页面视图将获得更新后的ACR)?如果确实这样做了,那么它们不是简单的ACL又有什么关系呢?我对该信息的相关性有些困惑... - ircmaxell
我通常缓存的是个人用户和组织之间注册的关系。注册与组织的关系的个人被允许操作该组织的工具。如果我要缓存ACR,我必须缓存每个组织/工具组合,并且每次添加新工具时都必须扩展会话管理类。有些情况下,我需要偏离这种模式,比如确定用户是否是组所有者。但大多数情况下,这个模型是自动可扩展的。 - coolgeek

2

虽然我的方法并不完全适用于Code Igniter,但我会尽力帮忙解决这个问题。

过去,我通常采用以下方法解决此类问题:创建一个用户对象模型,其构造函数从存储凭据的数据库主键中获取UserID。我将编写一个静态登录方法,检查登录凭据,如果行中登录正确,则实例化并返回一个用户实例,然后设置会话。

到目前为止都很好,对吧?因此,所有您的权限、访问级别等信息都存储在数据库中。就像有一个登录方法一样,我们可以有一个刷新方法,重新实例化对象,从已经获得的主键重新获取数据。

class User{
public function __construct($uid){
  //fetch from the db here
  $sql = 'SELECT FROM User_Table WHERE UserID = ?';
  $params = array($uid);
  //fetch and assign using your flavor of database access, I use PDO
  //set all your object properties for access, as well as user_id, something like 
  //$this->user_id = $result['UserID'];
}

public static function Login($uname, $pass){
  $sql = 'SELECT UserID FROM User WHERE Username = ? AND Password = ?';
  $params = array($uname, md5($pass));
  //again I'm going to employ pseudocode here, fetch according to your preferred system
  if(!empty($result)){
    $_SESSION['user'] = new User($result['UserID']);
  }else{
     //login failed!
     return false;   
  }
}

final public function _refresh(){
  //refresher method.  Since the controller sets all the object properties for access
  //reconstructing and assigning it refreshes these priveliges.
  $_SESSION['user'] = new User($this->user_id);
  }

}

使用这个模型,每当我在会话中与用户执行可能需要时间敏感权限的操作时,我可以调用会话中已准备好的用户对象上的刷新。假设我们有一个控制器函数来访问受限表单。

function _topSecret(){
$_SESSION['user']->refresh();

if($_SESSION['user']->specific_permission_from_db){
  //Perform the special action, get the view, whatever.
}else{
  //redirect to an error page
}

}

所以,如果您已经为管理员编写了例行程序,他们只需要在数据库中设置用户的权限,并在特定的时间敏感功能上运行刷新方法,那么该数据将被放入用户的会话中。因此,您不一定要更新另一个用户的会话,您只需确定哪些操作对时间顺序敏感,需要最新的权限,并向会话中的用户对象发出该命令。
这对我来说非常有效,因为重构只需要在需要最新权限的操作上执行。再次强调,我不确定它在CI和您的个人应用程序的上下文中有多大用处,但我想分享一下。我假设您已经执行了所有必要的会话安全措施并启动了会话 - 大多数框架(如CI)都会通过其本地手段为您处理此问题。

+1. 这是一个很好的答案。但是,1)我没有使用User对象,而是使用CI会话对象。2)从阅读答案中我逐渐明白,我的核心问题是a)找到正确的会话进行更新,以及b)需要重写我的会话get/set函数来接受用户ID,而不是使用当前用户的ID。 - coolgeek
我明白你的意思,你想要操作实际的会话,而不是强制客户端访问查找最新数据。我认为这可能会在某种程度上证明有用。谢谢,祝好运! - DeaconDesperado

1
在会话表中添加一个列,可以保存用户对象的主键。在登录时设置它,并在稍后用它来更新会话。
编辑:如果您不想扩展会话表,请创建一个额外的查找表,在其中链接用户对象ID和会话密钥。

如果这是我自己编写的会话库,用户ID已经作为外键存储在表中了 ;) 但是我正在使用本地CI会话功能,并且我非常反对扩展核心功能。 - coolgeek
同一个计划,不同的方法 :) - Knubo

0
如果您将会话存储为本地php会话,我倾向于不要去修改它,并允许用户得到某种提示,告诉他们需要登录/退出以刷新设置。我也有一些网站,其中会话数据存储在数据库中,这时只需轻松地编写新的设置即可。

它在数据库中。我只需要摆脱“无论如何都要避免全文搜索”的思维定势。 - coolgeek

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接