Подтвердить что ты не робот

Когда один и тот же идентификатор пользователя пытается войти на несколько устройств, как мне убить сеанс на другом устройстве?

Что я хочу сделать, так это ограничить идентификатор пользователя только возможностью входа в систему на одном устройстве за раз. Например, идентификатор пользователя "abc" входит в систему на свой компьютер. Идентификатор пользователя "abc" теперь пытается войти в систему со своего телефона. Я хочу, чтобы это произошло, чтобы убить сеанс на своем компьютере.

Приложение Spotify выполняет именно это: Spotify позволяет одновременно регистрировать один идентификатор пользователя на одном устройстве.

Я использую членство ASP.NET(SqlMembershipProvider) и Аутентификацию форм.

Я экспериментировал с переменными сеанса, но я не уверен, куда именно отсюда.

4b9b3361

Ответ 1

Я придумал довольно устрашающее решение. То, что я реализовал, - это когда пользователь "Боб" входит в систему со своего ПК, а затем тот же пользователь "Боб" входит в систему из другого места, вход в систему из первого места (их ПК) будет убит, а второй войдите в систему, чтобы жить. Как только пользователь входит в систему, он вставляет запись в пользовательскую таблицу, созданную мной под названием "Логины". При успешном входе в эту таблицу будет вставлена ​​одна запись со значениями для "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();

В моей ситуации я хочу поместить чек на каждом из моих контроллеров, чтобы узнать, зарегистрирован ли текущий зарегистрированный пользователь в другом месте, и если это так, убейте другие сеансы. Затем, когда убитый сеанс пытается перемещаться в любом месте, я разместил эти проверки, он выйдет из системы и перенаправит их на экран входа в систему.

У меня есть три основных метода, которые выполняют эти проверки:

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;
...

Код контроллера

Затем я помещаю логику внутри своих контроллеров для управления потоком выполнения этих трех методов. Обратите внимание, что если по какой-либо причине 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> 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();
}

EDIT Я просто хочу добавить, что этот код игнорирует возможности функции "Запомнить меня". Мое требование не связано с этой функцией (на самом деле, мой клиент не хотел использовать его по соображениям безопасности), поэтому я просто его не выпускал. Однако с некоторым дополнительным кодированием я вполне уверен, что это можно было бы принять во внимание.

Ответ 2

Вам нужно будет сохранить информацию, которую кто-то зарегистрировал в базе данных. Это позволит вам проверить, имеет ли пользователь уже существующий сеанс. Из коробки модуль проверки подлинности форм в ASP.NET работает с кукисами, и нет никакого способа узнать на сервере, есть ли у пользователя файлы cookie на других устройствах, если, конечно, вы не храните эту информацию на сервере.

Ответ 3

То, что вы, вероятно, захотите сделать, - это когда пользователь входит в систему, вы где-то сохраняете свой идентификатор сеанса в базе данных. Затем на каждой доступной странице вы должны проверить, совпадает ли текущий идентификатор сеанса с тем, что хранится в базе данных, и если вы не подписываете их.

Возможно, вам захочется создать базовый контроллер, который делает это в методах OnAuthorization или OnActionExecuting. Другой вариант - создать свой собственный фильтр авторизации (я бы предпочел, чтобы я сам, поскольку мне не нравятся общие базовые классы).

В этом методе вы должны получить доступ к базе данных и проверить идентификатор сеанса.

Имейте в виду, что он не является надежным. Возможно, кто-то скопирует куки файл сеанса и обойдется в этом, хотя это недостаточно понятно, что большинство людей, вероятно, не знают, как это сделать, и досадно, что те, кто это делает, не будут беспокоиться.

Вы также можете использовать IP-адрес, но эту же сделку. Два человека за прокси-сервером или nat-брандмауэром кажутся одним и тем же пользователем.

Ответ 4

Я хотел бы указать, что ключевая причина для установки Session [ "SessionID" ] = "anything" заключается в том, что до тех пор, пока вы фактически не назначили что-либо в объекте сеанса, идентификатор сеанса, похоже, будет меняться при каждом запросе.

Я столкнулся с этим с помощью программного обеспечения для раздельного тестирования, которое я пишу.

Ответ 5

Здесь метод, который немного проще принятого ответа.

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

При этом во время процесса регистрации после того, как вы проверили пользователя, вы создаете экземпляр класса User и назначаете ему имя пользователя и идентификатор сеанса, сохраняете его как объект Session, а затем вызываете с ним функцию RegisterLogin.

Затем на каждой загрузке страницы вы получаете объект сеанса и передаете его функции ValidateCurrentLogin.

Функция DeregisterLogin не является строго необходимой, но сохраняет объект _sessions как можно меньше.