web-dev-qa-db-fra.com

Lorsque le même ID utilisateur tente de se connecter sur plusieurs périphériques, comment puis-je tuer la session sur l'autre périphérique?

Ce que je veux faire est de limiter un ID utilisateur à la possibilité de se connecter à un seul périphérique à la fois. Par exemple, l'ID utilisateur "abc" se connecte à son ordinateur. L'identifiant "abc" tente maintenant de se connecter depuis son téléphone. Ce que je veux, c'est tuer la session sur leur ordinateur.

L'application Spotify fait exactement cela: Spotify n'autorise qu'un seul ID utilisateur à se connecter sur un appareil à la fois.

J'utilise une adhésion ASP.NET (SqlMembershipProvider) et l'authentification par formulaires.

J'ai expérimenté des variables de session mais je ne sais pas exactement où aller à partir de maintenant.

32
Mike Marks

J'ai trouvé une solution assez impressionnante à cela. Ce que j’ai implémenté, c’est lorsque l’utilisateur "Bob" se connecte à partir de son PC, puis que le même utilisateur "Bob" se connecte depuis un autre emplacement. La connexion depuis le premier connectez-vous pour vivre. Une fois qu'un utilisateur se connecte, il insère un enregistrement dans une table personnalisée que j'ai créée et intitulée "Connexions". Une fois la connexion réussie, un enregistrement sera inséré dans cette table avec les valeurs "UserId, SessionId et LoggedIn". UserId est assez explicite, SessionId est l'identifiant de session actuel (expliqué ci-dessous comment l'obtenir) et LoggedIn est simplement un booléen initialement défini sur True lors d'une connexion réussie. Je place cette logique "d'insertion" dans ma méthode de connexion de AccountController après validation de l'utilisateur - voir ci-dessous:

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

Pour ma situation, je veux vérifier chacun de mes contrôleurs pour voir si l'utilisateur actuellement connecté est connecté ailleurs et, si c'est le cas, tuez les autres sessions. Ensuite, lorsque la session tuée tente de naviguer où que je sois placé, elle les déconnecte et les redirige vers l'écran de connexion.

J'ai trois méthodes principales qui effectue ces vérifications:

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

Enregistrer l'identifiant de session dans la session ["..."]

Avant tout, j’enregistre le SessionID dans la collection Session à l’intérieur du AccountController, dans la méthode Login ([HttpPost]):

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

Code du contrôleur

Je place ensuite une logique à l'intérieur de mes contrôleurs pour contrôler le déroulement de l'exécution de ces trois méthodes. Notez ci-dessous que si pour une raison quelconque, Session["sessionid"] est null, il lui suffira simplement de lui attribuer une valeur "vide". Ceci est juste au cas où pour une raison quelconque, il revienne comme nul:

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

Les trois méthodes

Ce sont les méthodes que j'utilise pour vérifier si VOUS êtes toujours connecté (c.-à-d. Assurez-vous de ne pas avoir été lancé par une autre tentative de connexion), et si c'est le cas, vérifiez si votre ID utilisateur est connecté ailleurs et, le cas échéant, démarrez-les simplement en définissant leur état LoggedIn sur false dans le tableau Connexions.

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

EDITJe veux juste ajouter que ce code ignore la capacité de la fonctionnalité "Remember Me". Mon besoin ne portait pas sur cette fonctionnalité (en fait, mon client ne voulait pas l'utiliser, pour des raisons de sécurité), alors je l'ai simplement laissé de côté. Avec quelques codages supplémentaires cependant, je suis à peu près certain que cela pourrait être pris en compte.

34
Mike Marks

Vous devrez stocker les informations indiquant qu'une personne s'est connectée à la base de données. Cela vous permettrait de vérifier si l'utilisateur a déjà une session existante. Le module d'authentification par formulaire dans ASP.NET fonctionne avec les cookies et vous ne pouvez pas savoir sur le serveur si l'utilisateur a des cookies sur d'autres appareils, à moins bien sûr de stocker ces informations sur le serveur.

7
Darin Dimitrov

Ce que vous voulez probablement faire est que lorsqu'un utilisateur se connecte, vous sauvegardez son identifiant de session quelque part dans la base de données. Ensuite, sur chaque page à laquelle vous accédez, vous devez vérifier si l'ID de la session en cours est identique à ce qui est stocké dans la base de données et sinon, vous les déconnectez.

Vous voudrez probablement créer un contrôleur de base qui le fait dans les méthodes OnAuthorization ou OnActionExecuting. Une autre option serait de créer votre propre filtre d'autorisation (je préférerais le faire moi-même, car je n'aime pas les classes de base communes comme ça).

Avec cette méthode, vous accéderiez à la base de données et vérifieriez l'ID de session.

Sachez qu'il n'est pas infaillible. Il est possible pour quelqu'un de copier le cookie de session et de contourner ce problème, bien que cela soit assez obscur pour que la plupart des gens ne sachent probablement pas comment faire cela, et assez énervant pour que ceux qui le font ne se dérangent pas.

Vous pouvez également utiliser l'adresse IP, mais c'est la même affaire. Deux personnes derrière un proxy ou un pare-feu nat semblent être le même utilisateur.

5
Erik Funkenbusch

J'aimerais souligner que l'une des principales raisons pour définir Session ["SessionID"] = "n'importe quoi" est que tant que vous n'affectez pas quelque chose à l'objet de session, l'ID de session semble changer constamment à chaque demande.

Je me suis heurté à cela avec un logiciel de test partagé que j'écris.

1
Roger Willcocks

Voici une méthode légèrement plus simple que la réponse acceptée.

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

Avec cela, lors de votre processus de connexion après avoir vérifié l'utilisateur, vous créez une instance de la classe User et lui attribuez le nom d'utilisateur et l'identifiant de session, enregistrez-la en tant qu'objet Session, puis appelez la fonction RegisterLogin avec elle.

Ensuite, à chaque chargement de page, vous obtenez l'objet de session et le transmettez à la fonction ValidateCurrentLogin.

La fonction DeregisterLogin n'est pas strictement nécessaire, mais conserve l'objet _sessions aussi petit que possible.

0
Kevin