当同一用户ID尝试登录多台设备时,如何终止其他设备上的会话?

Mik*_*rks 33 c# asp.net-membership session-state sqlmembershipprovider asp.net-mvc-4

我想要做的是将用户ID限制为只能一次登录到一个设备.例如,用户ID"abc"登录到他们的计算机.用户ID"abc"现在尝试从手机登录.我想要发生的是在他们的计算机上杀死会话.

Spotify应用就是这样做的 - Spotify只允许一个用户ID一次登录到一个设备上.

我正在使用ASP.NET成员资格(SqlMembershipProvider)和表单身份验证.

我已尝试使用Session变量,但我不确定从何处开始.

Mik*_*rks 36

我想出了一个非常棒的解决方案.我实现的是当用户"Bob"从他们的PC登录,然后同一用户"Bob"从另一个位置登录时,从第一个位置(他们的PC)登录将被杀死,同时允许第二个位置登录直播.用户登录后,会将记录插入到我创建的名为"登录"的自定义表中.登录成功后,将在此表中插入一条记录,其值为"UserId,SessionId和LoggedIn".UserId非常明显,SessionId是当前的会话ID(下面将介绍如何获取),而LoggedIn只是一个布尔值,在用户成功登录后最初设置为True.在成功验证用户后,我将此"插入"逻辑放在我的AccountController的Login方法中 - 见下文:

Logins login = new Logins();
login.UserId = model.UserName;
login.SessionId = System.Web.HttpContext.Current.Session.SessionID;;
login.LoggedIn = true;

LoginsRepository repo = new LoginsRepository();
repo.InsertOrUpdate(login);
repo.Save();
Run Code Online (Sandbox Code Playgroud)

对于我的情况,我想检查我的每个控制器,看看当前登录的用户是否在其他地方登录,如果是,则杀死其他会话.然后,当被杀死的会话尝试在我放置这些检查的任何地方导航时,它会将它们记录下来并将它们重定向到登录屏幕.

我有三种主要方法来执行这些检查:

IsYourLoginStillTrue(UserId, SessionId);
IsUserLoggedOnElsewhere(UserId, SessionId);
LogEveryoneElseOut(UserId, SessionId);
Run Code Online (Sandbox Code Playgroud)

将会话ID保存到会话["..."]

在此之前,我将SessionID保存到AccountController内的Session集合中,在Login([HttpPost])方法内:

if (Membership.ValidateUser(model.UserName, model.Password))
{
     Session["sessionid"] = System.Web.HttpContext.Current.Session.SessionID;
...
Run Code Online (Sandbox Code Playgroud)

控制器代码

然后我将逻辑放在我的控制器中以控制这三种方法的执行流程.注意低于如果由于某种原因Session["sessionid"]null,它会只是单纯的将其指定的"空"的值.这只是出于某些原因它返回null:

public ActionResult Index()
{
    if (Session["sessionid"] == null)
        Session["sessionid"] = "empty";

    // check to see if your ID in the Logins table has LoggedIn = true - if so, continue, otherwise, redirect to Login page.
    if (OperationContext.IsYourLoginStillTrue(System.Web.HttpContext.Current.User.Identity.Name, Session["sessionid"].ToString()))
    {
        // check to see if your user ID is being used elsewhere under a different session ID
        if (!OperationContext.IsUserLoggedOnElsewhere(System.Web.HttpContext.Current.User.Identity.Name, Session["sessionid"].ToString()))
        {
            return View();
        }
        else
        {
            // if it is being used elsewhere, update all their Logins records to LoggedIn = false, except for your session ID
            OperationContext.LogEveryoneElseOut(System.Web.HttpContext.Current.User.Identity.Name, Session["sessionid"].ToString());
            return View();
        }
    }
    else
    {
        FormsAuthentication.SignOut();
        return RedirectToAction("Login", "Account");
    }
}
Run Code Online (Sandbox Code Playgroud)

三种方法

这些是我用来检查您是否仍然登录的方法(即确保您没有被其他登录尝试启动),如果是,请检查您的用户ID是否已记录在其他位置如果是这样,只需false在Logins表中设置LoggedIn状态即可启动它们.

public static bool IsYourLoginStillTrue(string userId, string sid)
{
    CapWorxQuikCapContext context = new CapWorxQuikCapContext();

    IEnumerable<Logins> logins = (from i in context.Logins
                                  where i.LoggedIn == true && i.UserId == userId && i.SessionId == sid
                                  select i).AsEnumerable();
    return logins.Any();
}

public static bool IsUserLoggedOnElsewhere(string userId, string sid)
{
    CapWorxQuikCapContext context = new CapWorxQuikCapContext();

    IEnumerable<Logins> logins = (from i in context.Logins
                                  where i.LoggedIn == true && i.UserId == userId && i.SessionId != sid
                                  select i).AsEnumerable();
    return logins.Any();
}

public static void LogEveryoneElseOut(string userId, string sid)
{
    CapWorxQuikCapContext context = new CapWorxQuikCapContext();

    IEnumerable<Logins> logins = (from i in context.Logins 
                                  where i.LoggedIn == true && i.UserId == userId && i.SessionId != sid // need to filter by user ID
                                  select i).AsEnumerable();

    foreach (Logins item in logins)
    {
        item.LoggedIn = false;
    }

    context.SaveChanges();
}
Run Code Online (Sandbox Code Playgroud)

编辑我还想补充一点,这段代码忽略了"记住我"功能的功能.我的要求没有涉及这个功能(事实上,出于安全原因,我的客户不想使用它)所以我就把它留了出来.但是通过一些额外的编码,我很确定这可以考虑到.

  • 您可能希望将该片段重写为`return logins.Any(i => i.LoggedIn == true && i.UserId == userId && i.SessionId!= sid);` (3认同)
  • @MikeMarks如果你有工作样品也分享它.它对许多人都有用. (3认同)
  • @MikeMarks这正是我提问的主题.我正在寻找一个可以挂钩"自动登录"并添加代码来创建登录行的地方.也许别人可以帮忙?无论如何,谢谢你的快速回答. (2认同)
  • 我有类似的实现,登录后我将GUID和LoginName插入/更新到表中.然后将guid设置为cookie.在我的网络中,我有一个轮询功能,定期检查用户是否仍然有效.如果Guids不匹配他无效,如果表没有登录,我只是认为他是有效的.这是为了防止重复登录而不是用于身份验证/授权.现在我正在使用信号器,我会增强这一点,所以登录后它可以踢出其他用户而无需轮询服务器. (2认同)

Dar*_*rov 7

您必须将某人登录的信息存储到数据库中.这将允许您验证用户是否已有现有会话.开箱即用的ASP.NET中的表单身份验证模块使用cookie,您无法在服务器上知道用户是否在其他设备上有cookie,除非您将此信息存储在服务器上.


Eri*_*sch 5

您可能想要做的是当用户登录时,您将会话ID保存在某个地方的数据库中.然后在您访问的每个页面上,您必须检查当前会话ID是否与存储在数据库中的内容相同,如果不是,则将其签名.

您可能希望创建一个在OnAuthorization或OnActionExecuting方法中执行此操作的基本控制器.另一个选择是创建自己的授权过滤器(我更喜欢自己,实际上,因为我不喜欢像这样的常见基类).

在该方法中,您将访问数据库并检查会话ID.

请注意,他并非万无一失.有人可能会复制会话cookie并解决这个问题,尽管这个模糊不清以至于大多数人可能不会知道如何做到这一点,而且非常烦人,那些做不会打扰的人.

你也可以使用IP地址,但这是同样的交易.代理或nat防火墙后面的两个人似乎是同一个用户.