如何确保用户仅登录一次?
几年前,我开发了一个Web应用程序,我们想要确保该用户不共享凭据。
我们决定要做的一件事就是只允许用户一次从一台计算机上登录。我这样做的方法是每N秒对服务器进行一点iframe ping操作;只要服务器具有特定用户的心跳(来自特定IP),就不允许该用户从任何其他IP登录。
该解决方案尽管得到了我的经理的认可,但对我而言似乎总是很棘手。另外,似乎很容易规避。
有没有一种好的方法来确保Web应用程序用户仅登录一次?老实说,我从来不明白为什么管理层甚至想要此功能。在分布式应用程序上强制执行此操作是否有意义?
解决方案
在高度安全的应用程序中,可能会要求我们这样做。我们可以做的是保持登录计数递增,以增加登录用户和IP地址的数量。计数永远不能为2. 如果是,则我们注销另一个IP,并且无论该IP登录到谁,该IP都会被丢弃。这不会阻止用户1将其凭据提供给用户2,如果用户2同时登录其他地方,只会使用户1难以完成其工作。
我从未找到解决此问题的标准解决方案。在我的一个应用程序中,我结合使用Javascript和Java来确保只能从指定IP(实际上是会话ID)记录一次用户,但在最坏的情况下会超时(设置为2分钟)无法使用该帐户。
我不为什么没有通用的方法来做到这一点。
我通过维护当前登录用户的哈希表来实现这一点,键是用户名,值是他们的上次活动时间。
登录时,我们只需要检查此哈希表中的密钥即可,如果该密钥存在,则拒绝登录。
当用户执行任何操作时,我们将随时间更新哈希表(如果使其成为核心页面框架的一部分,这将很容易)。
如果哈希表中的时间大于20分钟的闲置时间,则将其删除。我们可以在每次检查哈希表时执行此操作,因此,即使我们只有一个用户,并且尝试在几小时后登录,在该初始检查期间,也会将其从哈希表中删除,以保持空闲状态。
C(未经测试)中的一些示例:
public Dictionary<String,DateTime> UserDictionary { get { if (HttpContext.Current.Cache["UserDictionary"] != null) { return HttpContext.Current.Cache["UserDictionary"] as Dictionary<String,DateTime>; } return new Dictionary<String,DateTime>(); } set { HttpContext.Current.Cache["UserDictionary"] = value; } } public bool IsUserAlreadyLoggedIn(string userName) { removeIdleUsers(); return UserDictionary.ContainsKey(userName); } public void UpdateUser(string userName) { UserDictionary[userName] = DateTime.Now; removeIdleUsers(); } private void removeIdleUsers() { for (int i = 0; i < UserDictionary.Length; i++) { if (user[i].Value < DateTime.Now.AddMinutes(-20)) user.RemoveAt(i); } }
只看IP是不可靠的。 IIRC有一些代理样式,可以通过多个IP地址随机处理传出请求。根据应用范围,这可能会或者可能不会影响我们。其他代理将显示来自单个IP的大量流量。
上次登录时间也可能是一个问题。考虑基于cookie的身份验证,其中身份验证cookie并不是持久的(一件好事)。如果浏览器崩溃或者关闭,则用户必须重新登录,但直到超时到期后才能登录。如果该应用程序用于交易股票,则20分钟的不工作时间会花费金钱,这可能是不可接受的。
通常,可以购买比我们或者我做得更好的智能防火墙/路由器做得更好。它们还有助于防止重放攻击,cookie窃取等,并且可以配置为与所选Web平台中的标准机制一起运行。
我只是有这个问题。
我们正在构建一个包含Flex应用程序(由客户端构建)的Drupal网站,他想要以下内容:
- 从Drupal <-> Flex透明登录(真好!)
- 没有并发登录!
他测试了每种解决方案中的废话,最后,这就是我们所做的:
- 我们通过每个URL传递了会话ID。
- 当用户登录时,我们建立了一个时间戳-IP-SessionID-用户名足迹
- 每个页面都对数据库执行了ping操作,并且如果在同一IP上找到同一用户且具有不同的SessionID,则将引导他们的较早用户
该解决方案满足了客户的严格测试要求(他家中有2台计算机。在我们提出此解决方案之前,他让我们工作了几个小时才发现代码中的小问题和缝隙)
我会解决这个问题,并允许以任何较早的登录为代价来允许最后一次登录,因此,每当用户登录时,都要终止他可能拥有的任何其他登录会话。
它实施起来非常容易,我们最终会知道自己在哪里。
被警告过类似的"特征",这是一个边缘案例的陷阱,我们最终以为自己被钉上了,然后我们或者其他人说"但是如果有人做了X会怎样?"并且我们意识到我们必须增加另一层复杂性。
例如:
- 如果用户打开带有会话副本的新选项卡怎么办?
- 如果用户打开一个新的浏览器窗口怎么办?
- 如果用户登录并且浏览器崩溃怎么办?
等等...
基本上,有一系列或者多或者少的hacky解决方案,它们都不是万无一失的,所有这些都将很难维护。通常,客户的真正目标是其他一些合法的安全目标,例如"停止用户共享帐户"。
最好的主意是找出根本目标是什么,并找到实现该目标的方法。而且我担心这涉及谈判外交和其他"软技能",而不是进行技术性的追赶。