Когда один и тот же идентификатор пользователя пытается войти на несколько устройств, как мне убить сеанс на другом устройстве?
То, что я хочу сделать, – это ограничить идентификатор пользователя только возможностью входа в систему на одном устройстве за раз. Например, идентификатор пользователя «abc» входит в систему на свой компьютер. Идентификатор пользователя «abc» теперь пытается войти в систему со своего телефона. То, что я хочу сделать, – это убить сеанс на своем компьютере.
Приложение Spotify выполняет именно это: Spotify позволяет одновременно регистрировать один идентификатор пользователя на одном устройстве.
Я использую членство ASP.NET (SqlMembershipProvider) и Forms Authentication.
- Создание нового сеанса ASP.NET в текущем HTTPContext
- Включение / выключение состояния сеанса для каждого controllerа / метода действий
- Поддержание сеанса в android (приложение остается аутентифицированным на стороне сервера)
- Как отключить состояние сеанса в ASP.NET MVC?
- ASP.NET: как получить доступ к сеансу из обработчика?
Я экспериментировал с переменными сеанса, но я не уверен, где именно перейти отсюда.
- Недействительный сеанс JWT на стороне клиента
- Разрешение сеанса в веб-ферме? Достаточно ли StateServer?
- Могу ли я получить доступ к состоянию сеанса из HTTPModule?
- SignalR не использует сеанс на сервере
- Веб-приложение заблокировано при обработке другого веб-приложения при совместном использовании одного сеанса
Я придумал довольно удивительное решение. То, что я реализовал, – это когда пользователь «Боб» входит в систему со своего ПК, а затем тот же пользователь «Боб» входит в систему из другого места, вход в систему из первого места (их ПК) будет убит, а второй войдите в систему, чтобы жить. Как только пользователь входит в систему, он вставляет запись в пользовательскую таблицу, созданную мной под названием «Логины». При успешном входе в эту таблицу будет вставлена одна запись со значениями для «UserId, SessionId и LoggedIn». UserId довольно понятен, SessionId – это текущий идентификатор сеанса (объясняется ниже, как получить), а LoggedIn – это просто логическое значение, которое изначально установлено на True при успешном входе в систему. Я помещаю эту логику «вставки» в мой метод входа в свой AccountController после успешной проверки пользователя – см. Ниже:
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();
В моей ситуации я хочу поместить чек на каждом из моих controllerов, чтобы проверить, зарегистрирован ли текущий зарегистрированный пользователь в другом месте, и если это так, убейте другие сеансы. Затем, когда убитый сеанс пытается перемещаться в любом месте, я разместил эти проверки, он выйдет из системы и перенаправит их на экран входа в систему.
У меня есть три основных метода, которые выполняют эти проверки:
IsYourLoginStillTrue(UserId, SessionId); IsUserLoggedOnElsewhere(UserId, SessionId); LogEveryoneElseOut(UserId, SessionId);
Сохранить идентификатор сеанса для сеанса [“…”]
Однако прежде всего я сохраняю SessionID в коллекции Session внутри AccountController внутри метода Login
( [HttpPost]
):
if (Membership.ValidateUser(model.UserName, model.Password)) { Session["sessionid"] = System.Web.HttpContext.Current.Session.SessionID; ...
Код controllerа
Затем я устанавливаю логику внутри своих controllerов для управления streamом выполнения этих трех методов. Обратите внимание, что если по какой-либо причине Session["sessionid"]
имеет значение null
, он просто просто присваивает ему значение «empty». Это на случай, если по какой-то причине оно возвращается как 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"); } }
Три метода
Это методы, которые я использую, чтобы проверить, все ли вы зарегистрированы (т. Е. Убедитесь, что вы не были вызваны другой попыткой входа в систему), и если да, проверьте, зарегистрирован ли ваш идентификатор пользователя в другом месте , и если это так, отбросьте их, просто установив для своего журнала LoggedIn значение false
в таблице Logins.
public static bool IsYourLoginStillTrue(string userId, string sid) { CapWorxQuikCapContext context = new CapWorxQuikCapContext(); IEnumerable 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 = (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 = (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(); }
EDIT Я просто хочу добавить, что этот код игнорирует возможности функции «Запомнить меня». Мое требование не связано с этой функцией (на самом деле, мой клиент не хотел использовать его по соображениям безопасности), поэтому я просто его не выпускал. Однако, с некоторым дополнительным кодированием, я вполне уверен, что это можно было бы принять во внимание.
Вам нужно будет сохранить информацию, которую кто-то зарегистрировал в базе данных. Это позволит вам проверить, имеет ли пользователь уже существующий сеанс. Из коробки модуль проверки подлинности форм в ASP.NET работает с файлами cookie, и вы не можете знать на сервере, есть ли у пользователя cookies на других устройствах, если, конечно, вы не храните эту информацию на сервере.
То, что вы, вероятно, захотите сделать, – это когда пользователь входит в систему, вы где-то сохраняете свой идентификатор сеанса в базе данных. Затем на каждой доступной странице вы должны проверить, совпадает ли текущий идентификатор сеанса с тем, что хранится в базе данных, и если вы не подписываете их.
Возможно, вам захочется создать базовый controller, который делает это в методах OnAuthorization или OnActionExecuting. Другой вариант – создать собственный фильтр авторизации (я бы предпочел, чтобы я сам, поскольку мне не нравятся общие базовые classы).
В этом методе вы должны получить доступ к базе данных и проверить идентификатор сеанса.
Имейте в виду, что он не является надежным. Кто-то может скопировать куки-файл сеанса и обойти это, хотя это недостаточно понятно, что большинство людей, вероятно, не знают, как это сделать, и досадно, что те, кто это делает, не будут беспокоиться.
Вы также можете использовать IP-адрес, но это та же самая сделка. Два человека, стоящих за прокси-сервером или брандмауэром, выглядят одинаково.
Я хотел бы указать, что ключевая причина для установки Session [“SessionID”] = “anything” заключается в том, что до тех пор, пока вы фактически не назначили что-либо в объект сеанса, идентификатор сеанса, похоже, будет меняться при каждом запросе.
Я столкнулся с этим с помощью программного обеспечения для раздельного тестирования, которое я пишу.
Вот метод, который немного проще, чем принятый ответ.
public static class SessionManager { private static List _sessions = new List (); 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; } }
При этом во время процесса регистрации после того, как вы проверили пользователя, вы создаете экземпляр classа User и назначаете ему имя пользователя и идентификатор сеанса, сохраняете его как объект Session, а затем вызываете с ним функцию RegisterLogin.
Затем на каждой загрузке страницы вы получаете объект сеанса и передаете его функции ValidateCurrentLogin.
Функция DeregisterLogin не является строго необходимой, но сохраняет объект _sessions как можно меньше.