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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/15903574/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-10 18:23:32  来源:igfitidea点击:

When the same user ID is trying to log in on multiple devices, how do I kill the session on the other device?

c#asp.net-mvc-4asp.net-membershipsession-statesqlmembershipprovider

提问by Mike Marks

What I want to do is to limit a user ID to only being able to log in to one device at a time. For example, user ID "abc" logs in to their computer. User ID "abc" now tries to log in from their phone. What I want to happen is to kill the session on their computer.

我想要做的是将用户 ID 限制为一次只能登录到一台设备。例如,用户 ID“abc”登录到他们的计算机。用户 ID“abc”现在尝试从他们的手机登录。我想要发生的是终止他们计算机上的会话。

The Spotify app does exactly this- Spotify only allows one User ID to be logged in on one device at a time.

Spotify 应用程序正是这样做的 - Spotify 一次只允许一个用户 ID 登录到一台设备上。

I'm using ASP.NET membership (SqlMembershipProvider) and Forms Authentication.

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

I've experimented with Session variables but I'm not sure exactly where to go from here.

我已经尝试过 Session 变量,但我不确定从这里开始。

采纳答案by Mike Marks

I came up with a pretty awesome solution to this. What I've implemented was when user "Bob" logs in from their PC, and then the same user "Bob" logs in from another location, the log-in from the first location (their PC) will be killed while allowing the second log-in to live. Once a user logs in, it inserts a record into a custom table I created called "Logins". Upon a successful log-in, one record will be inserted into this table with values for "UserId, SessionId, and LoggedIn". UserId is pretty self-explanatory, SessionId is the current Session ID (explained below how to get), and LoggedIn is simply a Boolean that's initially set to True upon a successful user log-in. I place this "insert" logic inside my Login method of my AccountController upon successful validation of the user- see below:

我想出了一个非常棒的解决方案。我实施的是当用户“Bob”从他们的 PC 登录,然后同一个用户“Bob”从另一个位置登录时,第一个位置(他们的 PC)的登录将被终止,同时允许第二个位置登录即可生活。用户登录后,它会将一条记录插入到我创建的名为“登录”的自定义表中。成功登录后,将在此表中插入一条记录,其中包含“UserId、SessionId 和 LoggedIn”的值。UserId 是不言自明的,SessionId 是当前的会话 ID(在下面解释如何获取),而 LoggedIn 只是一个布尔值,在用户成功登录时最初设置为 True。我把这个“插入”

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();

For my situation, I want to place the check on each of my controllers to see if the currently logged in user is logged in elsewhere, and if so, kill the other session(s). Then, when the killed session tries to navigate anywhere I placed these checks on, it'll log them out and redirect them to the Log-in screen.

对于我的情况,我想对我的每个控制器进行检查,以查看当前登录的用户是否在其他地方登录,如果是,则终止其他会话。然后,当被终止的会话试图导航到我放置这些检查的任何地方时,它会将它们注销并将它们重定向到登录屏幕。

I have three main methods that does these checks:

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

IsYourLoginStillTrue(UserId, SessionId);
IsUserLoggedOnElsewhere(UserId, SessionId);
LogEveryoneElseOut(UserId, SessionId);

Save Session ID to Session["..."]

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

Before all of this though, I save the SessionID to the Session collection inside the AccountController, inside the Login([HttpPost]) method:

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

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

Controller Code

控制器代码

I then place logic inside my controllers to control the flow of the execution of these three methods. Notice below that if for some reason Session["sessionid"]is null, it'll just simply assign it a value of "empty". This is just in case for some reason it comes back as null:

然后我在我的控制器中放置逻辑来控制这三个方法的执行流程。请注意,如果由于某种原因Session["sessionid"]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");
    }
}

The Three Methods

三种方法

These are the methods I use to check to see if YOU are still logged in (i.e. make sure you weren't kicked off by another log-in attempt), and if so, check to see if your User ID is logged in somewhere else, and if so, kick them off by simply setting their LoggedIn status to falsein the Logins table.

这些是我用来检查您是否仍然登录的方法(即确保您没有被另一次登录尝试踢掉),如果是,请检查您的用户 ID 是否在其他地方登录,如果是这样,只需将它们的 LoggedIn 状态设置false为 Logins 表中的即可。

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();
}

EDITI just also want to add that this code ignores the capability of the "Remember Me" feature. My requirement didn't involve this feature (in fact, my customer didn't want to use it, for security reasons) so I just left it out. With some additional coding though, I'm pretty certain that this could be taken into consideration.

编辑我还想补充一点,这段代码忽略了“记住我”功能的功能。我的要求不涉及此功能(实际上,出于安全原因,我的客户不想使用它)所以我只是将其忽略了。不过,通过一些额外的编码,我很确定可以考虑这一点。

回答by Darin Dimitrov

You will have to store the information that someone has logged in into the database. This would allow you to verify if the user already has an existing session. Out of the box the forms authentication module in ASP.NET works with cookies and there's no way for you to know on the server whether the user has cookies on other devices unless of course you store this information on the server.

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

回答by Erik Funkenbusch

What you probably want to do is when a user logs in, you save their session id in the database somewhere. Then on every page you access, you have to check if the current session id is the same as what's stored in the database, and if not you sign them out.

您可能想要做的是,当用户登录时,将他们的会话 ID 保存在数据库中的某个位置。然后在您访问的每个页面上,您必须检查当前会话 ID 是否与存储在数据库中的相同,如果不是,则将它们注销。

You will probably want to create a base controller that does this in the OnAuthorization or OnActionExecuting methods. Another option would be to create your own Authorization filter (i'd prefer that myself, actually, as I don't like common base classes like that).

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

In that method you would access the database and check the session id.

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

Be aware that his is not foolproof. It's possible for someone to copy the session cookie and get around this, though that's obscure enough that most people probably wouldn't know how to do that, and annoying enough that those that do wouldn't bother.

请注意,他并非万无一失。有人可能会复制会话 cookie 并解决这个问题,尽管这很模糊,以至于大多数人可能不知道该怎么做,而且很烦人,这样做的人不会打扰。

You could also use IP address, but that's the same deal. Two people behind a proxy or nat firewall would appear to be the same user.

您也可以使用 IP 地址,但这也是一样的。代理或 nat 防火墙后面的两个人看起来是同一个用户。

回答by Roger Willcocks

I would like to point out that a key reason for setting Session["SessionID"] = "anything" is because until you actually assign something into the session object the session ID seems to keep changing on every request.

我想指出,设置 Session["SessionID"] = "anything" 的一个关键原因是,在您实际将某些内容分配到会话对象之前,会话 ID 似乎在每次请求时都在不断变化。

I ran into this with some split testing software I write.

我使用我编写的一些拆分测试软件遇到了这个问题。

回答by Kevin

Here's a method that is slightly simpler than the accepted answer.

这是一种比接受的答案稍微简单的方法。

public static class SessionManager
    {
        private static List<User> _sessions = new List<User>();

        public static void RegisterLogin(User user)
        {
            if (user != null)
            {
                _sessions.RemoveAll(u => u.UserName == user.UserName);
                _sessions.Add(user);
            }
        }

        public static void DeregisterLogin(User user)
        {
            if (user != null)
                _sessions.RemoveAll(u => u.UserName == user.UserName && u.SessionId == user.SessionId);
        }

        public static bool ValidateCurrentLogin(User user)
        {
            return user != null && _sessions.Any(u => u.UserName == user.UserName && u.SessionId == user.SessionId);
        }
    }

    public class User {
        public string UserName { get; set; }
        public string SessionId { get; set; }
    }

With this, during your login process after you have verified the user, you create an instance of the User class and assign it the username and session id, save it as a Session object, then call RegisterLogin function with it.

这样,在验证用户后的登录过程中,创建 User 类的实例并为其分配用户名和会话 ID,将其保存为 Session 对象,然后使用它调用 RegisterLogin 函数。

Then, on each page load, you get the session object and pass it to the ValidateCurrentLogin function.

然后,在每次加载页面时,您都会获取会话对象并将其传递给 ValidateCurrentLogin 函数。

The DeregisterLogin function is not strictly necessary, but keeps the _sessions object as small as possible.

DeregisterLogin 函数不是绝对必要的,但使 _sessions 对象尽可能小。