2010-03-03 8 views
19

Ich verwende JSF 1.2 mit Richfaces und Facelets.Wie Sie eine Benutzersitzung ungültig machen, wenn er sich zweimal mit denselben Anmeldeinformationen anmeldet

Ich habe eine Anwendung mit vielen Session-Bereich-Beans und einigen Anwendungs-Beans.

Der Benutzer meldet sich an, sagen wir, Firefox. Eine Sitzung wird mit ID = "A" erstellt; Dann öffnet er Chrome und meldet sich erneut mit den gleichen Anmeldeinformationen an. Eine Sitzung wird mit ID = "B" erstellt.

Wenn die Sitzung "B" erstellt wird, möchte ich Sitzung "A" zerstören können. Wie geht das?

Auch. Wenn der Benutzer in Firefox etwas tut, möchte ich in der Lage sein, ein Popup oder eine Art Benachrichtigung anzuzeigen, die sagt "Du wurdest ausgeloggt, weil du dich von woanders eingeloggt hast".

Ich habe einen SessionListener, der die erstellten und zerstörten Sitzungen verfolgt. Die Sache ist, ich könnte das HTTPSession-Objekt in einer Application-Scoped-Bean speichern und es zerstören, wenn ich feststelle, dass der Benutzer sich zweimal angemeldet hat. Aber etwas sagt mir, das ist einfach falsch und wird nicht funktionieren.

Verfolgt JSF die Sitzungen irgendwo auf der Serverseite? Wie man auf sie durch Bezeichner zugreift? Wenn nicht, wie kann man das erste Einloggen eines Benutzers bei zweimaliger Anmeldung abbrechen?

Antwort

19

Der DB-unabhängige Ansatz wäre die User eine static Map<User, HttpSession> Variable haben zu lassen und HttpSessionBindingListener (und Object#equals() und Object#hashCode()) umzusetzen. Auf diese Weise funktioniert Ihre Webanwendung auch nach einem unvorhergesehenen Absturz, der dazu führen kann, dass die DB-Werte nicht aktualisiert werden (Sie können natürlich eine ServletContextListener erstellen, die die DB beim Start von webapp zurücksetzt, aber das funktioniert immer mehr).

Hier ist, wie die User aussehen sollte:

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

} 

Wenn Sie den User anmelden wie folgt:

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

dann wird es die valueBound() aufrufen, die in Benutzer alle zuvor angemeldet entfernen wird von der logins zuordnen und die Sitzung ungültig machen.

Wenn abzumelden Sie die User wie folgt:

sessionMap.remove("user"); 

oder wenn die Sitzung aus abgelaufen ist, dann wird die valueUnbound() aufgerufen werden, die den Benutzer aus der logins Karte entfernt.

+0

Danke für die Antwort. Ich denke, dass "sessionMap.put (" Benutzer ", Benutzer);" sollte "sessionMap.put (Benutzername, Benutzer);" sein. Andernfalls, wenn sich ein anderer Benutzer mit anderen Anmeldeinformationen anmeldet, würden wir den ersten Benutzer rausschmeißen. – pakore

+0

Das ist nicht normal. Sie möchten während einer Clientsitzung keine anderen angemeldeten Benutzer haben. Verwechseln Sie auch nicht die Sitzungszuordnung mit der Anwendungszuordnung. – BalusC

+0

Ok ich verstehe jetzt, dass sessionMap ist ExternalContext.sessionMap .Es funktioniert :). – pakore

4
  1. schaffen ein Integer-Feld in dem databse userLoggedInCount
  2. Auf jedem Login Schritt, die Flagge und das Ergebnis in der Sitzung speichern.
  3. Auf jeder Anforderung in der Datenbank den Wert zu überprüfen und die einen in der Sitzung, und wenn die ein in der Sitzung ist kleiner als die in der DB, invalidate() die Sitzung und verringert den Wert in der Datenbank
  4. , wenn ein Sitzung wird zerstört. Verringern Sie den Wert ebenfalls.
+0

statt von DB kann ich eine anwendungsspezifische Klasse verwenden, denke ich. Die Sache ist, wie man zB von einem Phasen-Listener darauf zugreift? Gute Lösung aber. – pakore

+0

Nun, Sie können den ExternalContext über den FacesContext erhalten – Bozho

+1

Dieser Algorithmus ist möglicherweise nicht ausreichend. Die Theorie ist perfekt, aber in der Praxis wird es nicht funktionieren, wenn der Browser die Sitzung in einem Cookie speichert und von ihm wiederherstellt. Werfen Sie einen Blick auf Schritt 2. Wenn, wie gesagt, die Browse die Sitzung automatisch wiederherstellt, wird sie keinen LoginHandler oder LoginMethod oder irgendeinen kontrollierbaren Ort durchlaufen, um das Inkrementieren dieses Flags durchzuführen. Wie handle ich in diesem Fall? – ElPiter

1

Ich mag die Antwort von BalusC mit einem HttpSessionBindingListener.

Aber in Enterprise JavaBeansTM Specification, Version 2.0 wird geschrieben:

Enterprise-Bean verwenden müssen nicht lesen/schreiben statische Felder. Die Verwendung schreibgeschützter statischer Felder ist zulässig. Daher wird empfohlen, dass alle statischen Felder in der Enterprise-Bean-Klasse werden als final deklariert

So isnt't es besser, eine ApplicationScoped Bean zu machen, die den Tisch Anwendung speichern breit ohne statische Felder mit ???

Es es ausprobiert und es scheint zu funktionieren ...

Hier ist mein Beispiel:

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

}

-> Sorry für meine Anglish ...

+0

Ein 'HttpSessionBindingListener' ist kein EJB. Nur Klassen mit einer 'javax.ejb. *' Hauptanmerkung wie '@ Stateless' sind EJBs. – BalusC