2010-03-03 5 views
19

J'utilise JSF 1.2 avec Richfaces et Facelets.Comment invalider une session utilisateur lorsqu'il se connecte deux fois avec les mêmes informations d'identification

J'ai une application avec de nombreux beans de session et certains beans d'application.

L'utilisateur se connecte avec, disons, Firefox. Une session est créée avec ID = "A"; Ensuite, il ouvre Chrome et se reconnecte avec les mêmes informations d'identification. Une session est créée avec ID = "B".

Lorsque la session "B" est créée, je veux être capable de détruire la session "A". Comment faire ça?

Aussi. Lorsque l'utilisateur de Firefox fait quelque chose, je veux être capable d'afficher une fenêtre contextuelle ou une sorte de notification disant "Vous avez été déconnecté parce que vous vous êtes connecté depuis un autre endroit".

J'ai un sessionListener qui garde la trace des sessions créées et détruites. Le truc, c'est que je pourrais sauvegarder l'objet HTTPSession dans un bean d'application et le détruire quand je détecterai que l'utilisateur s'est connecté deux fois. Mais quelque chose me dit que c'est juste faux et ne fonctionnera pas.

JSF conserve-t-il une trace des sessions quelque part sur le serveur? Comment y accéder par identifiant? Si non, comment expulser la première connexion d'un utilisateur lorsqu'il se connecte deux fois?

Répondre

19

L'approche DB indépendante serait de laisser la User ont une variable static Map<User, HttpSession> et mettre en œuvre HttpSessionBindingListener (et Object#equals() et Object#hashCode()). De cette façon, votre webapp fonctionnera toujours après un crash imprévu qui peut empêcher la mise à jour des valeurs DB (vous pouvez bien sûr créer un ServletContextListener qui réinitialise la base de données au démarrage de webapp, mais ce n'est que de plus en plus de travail).

Voilà comment le User devrait ressembler à:

public class User implements HttpSessionBindingListener { 

    // All logins. 
    private static Map<User, HttpSession> logins = new ConcurrentHashMap<>(); 

    // Normal properties. 
    private Long id; 
    private String username; 
    // Etc.. Of course with public getters+setters. 

    @Override 
    public boolean equals(Object other) { 
     return (other instanceof User) && (id != null) ? id.equals(((User) other).id) : (other == this); 
    } 

    @Override 
    public int hashCode() { 
     return (id != null) ? (this.getClass().hashCode() + id.hashCode()) : super.hashCode(); 
    } 

    @Override 
    public void valueBound(HttpSessionBindingEvent event) { 
     HttpSession session = logins.remove(this); 
     if (session != null) { 
      session.invalidate(); 
     } 
     logins.put(this, event.getSession()); 
    } 

    @Override 
    public void valueUnbound(HttpSessionBindingEvent event) { 
     logins.remove(this); 
    } 

} 

Lorsque vous vous connectez le User comme suit:

User user = userDAO.find(username, password); 
if (user != null) { 
    sessionMap.put("user", user); 
} else { 
    // Show error. 
} 

il invoquera la valueBound() qui supprimera tout utilisateur précédemment connecté de la logins mappe et invalide la session.

Lorsque vous déconnectez le User comme suit:

sessionMap.remove("user"); 

ou lorsque la session est expiré, le valueUnbound() sera appelée qui supprime l'utilisateur de la carte logins.

+0

Merci pour la réponse. Je suppose que "sessionMap.put (" utilisateur ", utilisateur);" devrait être "sessionMap.put (nom d'utilisateur, utilisateur);". Autrement, si un utilisateur différent avec des informations d'identification différentes se connecte, le premier utilisateur sera renvoyé. – pakore

+0

Ce n'est pas normal. Vous ne souhaitez pas avoir d'utilisateurs connectés différents pendant une session client. Ne confondez pas non plus la carte de session avec la carte d'application. – BalusC

+0

Ok, je comprends maintenant que sessionMap est ExternalContext.sessionMap. Cela fonctionne :). – pakore

4
  1. créer un champ entier dans le databse userLoggedInCount
  2. Sur chaque incrément de connexion que le drapeau et stocker le résultat dans la session.
  3. Sur chaque demande de vérifier la valeur de la base de données et celle de la session, et si celle de la session est inférieure à celle de la DB, invalidate() la session et décrémenter la valeur dans la base de données
  4. chaque fois qu'un la session est détruite, décrémentez la valeur aussi.
+0

place de DB je peux utiliser une classe d'application étendue je suppose. La chose est comment y accéder à partir d'un écouteur de phase par exemple? Bonne solution cependant. – pakore

+0

bien, vous pouvez obtenir le ExternalContext via le FacesContext – Bozho

+1

Cet algorithme peut ne pas être suffisant. La théorie est parfaite, mais dans la pratique, cela ne fonctionnera pas si le navigateur enregistre la session dans un cookie et la restaure. Jetez un coup d'oeil à l'étape 2. Si, comme je l'ai dit, la navigation restaure la session automatiquement, elle ne passera pas par LoginHandler ou LoginMethod ou n'importe quel endroit contrôlable pour effectuer l'incrément de ce drapeau. Comment agir dans ce cas? – ElPiter

1

J'aime la réponse de BalusC avec un HttpSessionBindingListener.

Mais dans Enterprise JavaBeansTM Spécification, version 2.0, il est écrit:

Bean entreprise ne doit pas utiliser la lecture/écriture des champs statiques. L'utilisation de champs statiques en lecture seule est autorisée. Par conséquent, il est recommandé que tous les champs statiques dans la classe bean entreprise sont déclarés comme finale

isnt't donc mieux de faire un Bean ApplicationScoped qui stockent l'application de table large, sans utiliser les champs statiques ???

Il ai essayé et il semble fonctionner ...

Voici mon exemple:

@Named 
@ApplicationScoped 
public class UserSessionStorage implements java.io.Serializable,HttpSessionBindingListener { 

@Inject 
UserManagement userManagement; 

private static final long serialVersionUID = 1L; 

/** 
* Application wide storage of the logins 
*/ 
private final Map<User, List<HttpSession>> logins = new HashMap<User, List<HttpSession>>(); 

@Override 
public void valueBound(final HttpSessionBindingEvent event) { 
    System.out.println("valueBound"); 

    /** 
    * Get current user from userManagement... 
    */ 
    User currentUser = userManagement.getCurrentUser(); 

    List<HttpSession> sessions = logins.get(currentUser); 
    if (sessions != null) { 
     for (HttpSession httpSession : sessions) { 
      httpSession.setAttribute("invalid", "viewExpired"); 
     } 
    } else { 
     sessions = new ArrayList<HttpSession>(); 
    } 
    HttpSession currentSession = event.getSession(); 
    sessions.add(currentSession); 
    logins.put(currentUser, sessions); 
} 

@Override 
public void valueUnbound(final HttpSessionBindingEvent event) { 
    System.out.println("valueUnbound"); 

    User currentUser = userManagement.getCurrentUser(); 

    List<HttpSession> sessions = logins.get(currentUser); 
    if (sessions != null) { 
     sessions.remove(event.getSession()); 
    } else { 
     sessions = new ArrayList<HttpSession>(); 
    } 
    logins.put(currentUser, sessions); 
} 

}

-> Désolé pour mon anglish ...

+0

Un 'HttpSessionBindingListener' n'est pas un EJB. Seules les classes avec une annotation principale 'javax.ejb. *' Telle que '@ Stateless' sont des EJB. – BalusC

Questions connexes