ASP.NET中每个用户仅允许一次并发登录

14

在 ASP.NET Web 应用程序中,是否可能允许每个用户仅有一个并发登录?

我正在开发一个Web应用程序,在这个应用程序中,我希望确保该网站每次只能允许一个用户登录。如何检查当前用户已经登录还是没有登录?

请建议适当的登录方法以解决此问题。我认为我们应该使用 SQL Server 会话状态来处理此问题。你有什么建议吗?

我想到了一种解决方案。我们可以像下面这样做:

  1. 当用户登录系统时,我们将会话ID插入到用户列中。(我们将使用数据库会话,以便我们可以轻松获取所有与会话相关的数据,例如isexpired、expiredatetime等)。

  2. 当同一用户尝试第二次登录时,我们将检查该会话ID列并检查该会话是否已过期。如果会话未过期,则不允许该用户登录。

  3. 每次用户注销时更新用户会话ID。

请建议这是否是正确的方式。


你正在使用哪种身份验证方式?标准的表单身份验证还是自定义的方式? - Markus-ipse
我们创建了登录表单,并制作了存储过程来检查用户的登录凭据。我们没有使用任何成员资格功能。 - Hiren Dhaduk
保存一个 isLoggedIn 属性,当您进行身份验证时将其提高到 1,当注销时降低到 0,如果会话在此期间结束,则需要重置所有内容,Membership 使用 LastLoginDate,您可以对其进行一些调整。 - balexandre
6个回答

9

5
你可以为每个用户创建一个缓存条目,并将其会话ID存储在其中。 会话ID将在每个浏览器会话中是唯一的。 在登录页面中,当他们成功登录时,您可以创建该缓存条目:
if(Cache.ContainsKey["Login_" + username])
    // Handle "Another session exists" case here
else
    Cache.Add("Login_" + username, this.Session.SessionID);

在文本框中输入的代码没有语法检查。假定这是“伪代码”。

在 global.asax 文件中,您可以钩入 Session_End 事件并使用户的缓存条目过期。点击此处查看 global.asax 事件。

if(Cache.ContainsKey["Login_" + username])
    Cache.Remove("Login_" + username);

为什么要使用Cache而不是Application?或者只是全局静态Dictionary - abatishchev
3
缓存的管理更加有效,并且可以通过负载均衡等方法实现在网络农场或多个线程之间进行同步。其他类型(静态或应用程序)仅处于进程中,在这些情况下不能保证单例对象。但是,如果情况不要求单例对象,您可以使用静态字典或应用程序对象。 - Tombala
1
缓存不是分布式的,只存在于Web服务器中,而不在Webfarm中。分布式缓存适用于所有Webfarm。 - Kiquenet
@Kiquenet 你说得对。我想的是Session状态。缓存不是分布式的。我应该知道。我已经使用了memcached和其他解决方案来克服这个限制。:( 请享用Klondike冰淇淋! - Tombala
@Kiquenet:你可以使用像http://memcached.org/这样的分布式缓存解决方案。然后在我的答案中,将所有引用“Cache”的内容替换为memcached。或者作为另一种选择,正如其他答案所说,您可以使用表来管理它,但是如果您有一个高流量的网站,请小心锁定/阻塞可能会成为问题。Memcached或类似的方式可能是更好的性能方式,但涉及更多设置。Klondike bar是美国的一种冰淇淋零食。他们的广告展示了某人做了什么好事,然后说:“给那个人一个Klondike bar!” - Tombala

2
您可以在用户表中添加一个标记列,用来表示用户当前是否已登录。
当用户尝试登录时,您可以检查该标记,如果标记为真(即该用户的帐户已经被使用),则不允许新用户登录;如果标记为假,则允许该用户登录,因为此时该帐户没有被其他人使用。
请注意,除非用户主动退出,否则您无法知道用户何时转到其他页面(转到其他网站或关闭浏览器等)。因此,您需要设置一些会话超时时间,在指定的时间内如果没有新请求,系统将自动注销用户。
这意味着,如果用户关闭了浏览器,然后在移动设备上尝试登录,他/她将无法登录,直到您指定的会话超时时间结束。因此,您需要认真考虑超时时间,不要让用户过快地被注销(例如,如果用户正在阅读长页面等),也不要让用户在离开家之前忘记注销而无法在其他设备上登录数小时。

1
我认为你可能不想将这样的数据持久化,而是将其保存在内存中(我会使用全局静态字典)。只要应用程序存在,你就关心它。一旦它消失了,你就不再关心它,因此不需要将其持久化。另外,想象一下回来的用户和未正确清除的数据库记录之间的问题。 - abatishchev
1
限制:全局静态变量在 Web Farm 场景下无法工作。如果会话是粘性的,可能会启动另一个会话,如果它“粘”到农场中的另一台服务器上,则两者都将被允许。如果没有粘性会话,则其他服务器上的全局静态变量将不会更新。全局静态变量还必须针对竞争条件进行隔离,特别是如果应用程序容易出现“抢地盘”登录激增的情况,其中大量用户需要/想要同时登录(例如,在指定时间开始的热门活动票务注册)。 - BrianCooksey

1
登录凭据存储在cookie上,因此要知道用户是否已登录,需要将这些信息保存在服务器上,最好是在数据库中,因为数据库可以是Web园或Web农场中唯一的共同位置。您可以在一个表格中记录用户A是否已登录,标记为已注销,可能是最后一次用户交互以进行超时等等。所以假设用户A已经登录,则在数据库中为该用户打开一个标志,表示该用户现在已登录,如果尝试再次登录,则让其无法登录。为了使其正常工作,您需要告诉用户退出登录,或者保持超时时间,类似于凭证的超时时间。

1
如果您正在使用身份验证系统,此链接将帮助您了解如何在多个设备上单个用户登录。 Asp.Net Identity中防止多次登录 我已经尝试过它们,在我的Asp.net Mvc项目中运行良好。

这似乎是一个不错的解决方案,但如果网站有大量用户频繁请求服务器,那么可能会变得混乱,因为每次请求时服务器都将验证“SecurityStamp”。 - Jamshaid K.

0
解决方案可以是这样的:

在您的登录表中添加新列 GuidCode
步骤1:登录时检查数据库中的 GuidCode 是否为 null
步骤2:通过新的 GUID 更新 GuidCode 并将其存储在会话中。
步骤3:如果它不是 null,则从会话中获取 GUID 并与数据库中的 GuidCode 值进行比较。
步骤4:如果相同,则允许登录:


如果用户在登录后会话过期并关闭了浏览器,会发生什么? - Muhammad Tahir

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