Das Abmeldeproblem richtig und elegant lösen

Viele Webanwendungen enthalten keine übermäßig vertraulichen und persönlichen Informationen wie Bankkontonummern oder Kreditkartendaten. Einige enthalten jedoch vertrauliche Daten, für die ein Kennwortschutz erforderlich ist. In einer Fabrik, in der Mitarbeiter eine Webanwendung zum Eingeben von Arbeitszeittabelleninformationen, zum Zugreifen auf ihre Schulungskurse und zum Überprüfen ihrer Stundensätze usw. verwenden müssen, wäre die Verwendung von SSL (Secure Socket Layer) zu viel des Guten (SSL-Seiten werden nicht zwischengespeichert) Die Diskussion über SSL würde den Rahmen dieses Artikels sprengen. Aber sicherlich erfordern diese Anwendungen eine Art Passwortschutz. Andernfalls würden Mitarbeiter (in diesem Fall die Benutzer der Anwendung) vertrauliche und vertrauliche Informationen über alle Fabrikmitarbeiter ermitteln.

Ähnliche Beispiele für die obige Situation sind mit dem Internet ausgestattete Computer in öffentlichen Bibliotheken, Krankenhäusern und Internetcafés. In solchen Umgebungen, in denen Benutzer einige gemeinsame Computer verwenden, ist der Schutz der persönlichen Daten der Benutzer von entscheidender Bedeutung. Gleichzeitig setzen gut gestaltete und gut implementierte Anwendungen nichts von den Benutzern voraus und erfordern den geringsten Schulungsaufwand.

Mal sehen, wie sich eine perfekte Webanwendung in einer perfekten Welt verhält: Ein Benutzer verweist seinen Browser auf eine URL. Die Webanwendung zeigt eine Anmeldeseite an, auf der der Benutzer aufgefordert wird, einen gültigen Berechtigungsnachweis einzugeben. Sie gibt die Benutzer-ID und das Passwort ein. Unter der Annahme, dass die angegebenen Anmeldeinformationen korrekt sind, ermöglicht die Webanwendung dem Benutzer nach dem Authentifizierungsprozess den freien Zugriff auf seine autorisierten Bereiche. Wenn es Zeit zum Beenden ist, drückt der Benutzer die Abmeldetaste der Seite. Die Webanwendung zeigt eine Seite an, auf der der Benutzer aufgefordert wird, zu bestätigen, dass er sich tatsächlich abmelden möchte. Sobald sie die OK-Taste drückt, endet die Sitzung und die Webanwendung zeigt eine weitere Anmeldeseite an. Der Benutzer kann sich jetzt vom Computer entfernen, ohne sich Sorgen machen zu müssen, dass andere Benutzer auf ihre persönlichen Daten zugreifen. Ein anderer Benutzer setzt sich an denselben Computer. Er drückt den Zurück-Knopf;Die Webanwendung darf keine der Seiten der Sitzung des letzten Benutzers anzeigen. Tatsächlich muss die Webanwendung die Anmeldeseite immer intakt halten, bis der zweite Benutzer einen gültigen Berechtigungsnachweis bereitstellt. Erst dann kann er seinen autorisierten Bereich besuchen.

In diesem Artikel wird anhand von Beispielprogrammen gezeigt, wie Sie ein solches Verhalten in einer Webanwendung erzielen.

JSP-Beispiele

Um die Lösung effizient zu veranschaulichen, werden in diesem Artikel zunächst die in der Webanwendung logoutSampleJSP1 aufgetretenen Probleme aufgeführt . Diese Beispielanwendung stellt eine Vielzahl von Webanwendungen dar, die den Abmeldevorgang nicht ordnungsgemäß ausführen. logoutSampleJSP1 besteht aus folgenden JSP (Java Server Pages) Seiten: login.jsp, home.jsp, secure1.jsp, secure2.jsp, logout.jsp, loginAction.jsp, und logoutAction.jsp. Die JSP - Seiten home.jsp, secure1.jsp, secure2.jsp, und logout.jspsind gegen unautorisierte Anwender geschützt, das heißt, sie sichere Informationen enthalten und sollte nie abmeldet auf den Browsern entweder bevor sich der Benutzer anmeldet oder nachdem der Benutzer angezeigt. Die Seite login.jspenthält ein Formular, in dem Benutzer ihren Benutzernamen und ihr Passwort eingeben. Die Seitelogout.jspenthält ein Formular, in dem Benutzer aufgefordert werden, zu bestätigen, dass sie sich tatsächlich abmelden möchten. Die JSP - Seiten loginAction.jspund logoutAction.jspwirken als die Controller und enthalten Code, der die Anmelde- und Abmelde Aktionen ausführt, respectively.

Eine zweite Beispiel-Webanwendung, logoutSampleJSP2, zeigt, wie das Problem von logoutSampleJSP1 behoben werden kann. LogoutSampleJSP2 bleibt jedoch problematisch. Das Abmeldeproblem kann sich unter bestimmten Umständen immer noch manifestieren.

Eine dritte Beispiel-Webanwendung, logoutSampleJSP3, verbessert logoutSampleJSP2 und stellt eine akzeptable Lösung für das Abmeldeproblem dar.

Ein letztes Beispiel für eine Webanwendung logoutSampleStruts zeigt, wie Jakarta Struts das Abmeldeproblem elegant lösen kann.

Hinweis: Die diesem Artikel beigefügten Beispiele wurden für die neuesten Browser von Microsoft Internet Explorer (IE), Netscape Navigator, Mozilla, FireFox und Avant geschrieben und getestet.

Anmeldeaktion

Brian Pontarellis ausgezeichneter Artikel "J2EE-Sicherheit: Container versus Benutzerdefiniert" beschreibt verschiedene J2EE-Authentifizierungsansätze. Wie sich herausstellt, bieten grundlegende und formularbasierte HTTP-Authentifizierungsansätze keinen Mechanismus für die Abmeldung. Die Lösung besteht daher darin, eine benutzerdefinierte Sicherheitsimplementierung zu verwenden, da diese die größte Flexibilität bietet.

Eine gängige Praxis beim benutzerdefinierten Authentifizierungsansatz besteht darin, Benutzeranmeldeinformationen aus einer Formularübermittlung abzurufen und mit den Backend-Sicherheitsbereichen wie LDAP (Lightweight Directory Access Protocol) oder RDBMS (Relational Database Management System) zu vergleichen. Wenn der angegebene Berechtigungsnachweis gültig ist, speichert die Anmeldeaktion ein Objekt im HttpSessionObjekt. Das Vorhandensein dieses Objekts in HttpSessionzeigt an, dass sich der Benutzer bei der Webanwendung angemeldet hat. Aus Gründen der Übersichtlichkeit speichern alle zugehörigen Beispielanwendungen nur die Benutzernamenzeichenfolge in der, HttpSessionum anzuzeigen, dass der Benutzer angemeldet ist. Listing 1 zeigt einen auf der Seite enthaltenen Codeausschnitt loginAction.jsp, um die Anmeldeaktion zu veranschaulichen:

Listing 1:

// ... // RequestDispatcher-Objekt initialisieren; Standardmäßig auf Startseite weiterleiten RequestDispatcher rd = request.getRequestDispatcher ("home.jsp"); // Verbindung und Anweisung vorbereiten rs = stmt.executeQuery ("Passwort vom USER auswählen, wobei userName = '" + userName + "'"); if (rs.next ()) {// Abfrage gibt nur 1 Datensatz in der Ergebnismenge zurück; nur 1 Passwort pro Benutzername, der auch der Primärschlüssel ist, wenn (rs.getString ("Passwort"). gleich (Passwort)) {// Wenn gültiges Passwort session.setAttribute ("Benutzer", Benutzername); // Speichert die Benutzernamenzeichenfolge im Sitzungsobjekt} else {// Passwort stimmt nicht überein, dh ungültiges Benutzerkennwort request.setAttribute ("Fehler", "Ungültiges Passwort."); rd = request.getRequestDispatcher ("login.jsp"); }} // Kein Datensatz in der Ergebnismenge, dhungültiger Benutzername else {request.setAttribute ("Fehler", "Ungültiger Benutzername."); rd = request.getRequestDispatcher ("login.jsp"); }} // Als Controller leitet loginAction.jsp schließlich entweder an "login.jsp" oder "home.jsp" weiter rd.forward (Anfrage, Antwort); // ...

In dieser und den übrigen zugehörigen Beispielwebanwendungen wird angenommen, dass der Sicherheitsbereich ein RDBMS ist. Das Konzept dieses Artikels ist jedoch transparent und auf jeden Sicherheitsbereich anwendbar.

Abmeldeaktion

Bei der Abmeldeaktion wird lediglich die Zeichenfolge für den Benutzernamen entfernt und die invalidate()Methode für das HttpSessionObjekt des Benutzers aufgerufen. Listing 2 zeigt ein Code-Snippet auf der Seite logoutAction.jsp, um die Abmeldeaktion zu veranschaulichen:

Listing 2:

// ... session.removeAttribute ("User"); session.invalidate (); // ...

Verhindern Sie den nicht authentifizierten Zugriff auf gesicherte JSP-Seiten

Zusammenfassend lässt sich sagen, dass bei einer erfolgreichen Überprüfung der aus der Formularübermittlung abgerufenen Anmeldeinformationen die Anmeldeaktion einfach eine Benutzernamenzeichenfolge in das HttpSessionObjekt einfügt . Die Abmeldeaktion macht das Gegenteil. Es entfernt die Benutzernamenzeichenfolge aus HttpSessionund ruft die invalidate()Methode für das HttpSessionObjekt auf. Damit sowohl die Anmelde- als auch die Abmeldeaktion überhaupt sinnvoll sind, müssen alle geschützten JSP-Seiten zuerst die darin enthaltene Benutzernamenzeichenfolge überprüfen, HttpSessionum festzustellen, ob der Benutzer derzeit angemeldet ist. Wenn sie HttpSessiondie Benutzernamenzeichenfolge enthält - ein Hinweis darauf, dass der Benutzer angemeldet ist - Die Webanwendung sendet den dynamischen Inhalt auf der restlichen JSP-Seite an die Browser. Andernfalls würde die JSP-Seite den Kontrollfluss zurück an die Anmeldeseite weiterleiten login.jsp. Die JSP - Seiten home.jsp, secure1.jsp,secure2.jspund logout.jspalle enthalten das in Listing 3 gezeigte Code-Snippet:

Listing 3:

// ... String userName = (String) session.getAttribute ("User"); if (null == userName) {request.setAttribute ("Fehler", "Sitzung wurde beendet. Bitte anmelden."); RequestDispatcher rd = request.getRequestDispatcher ("login.jsp"); rd.forward (Anfrage, Antwort); } // ... // Ermöglicht, dass der Rest des dynamischen Inhalts in dieser JSP dem Browser bereitgestellt wird // ...

Dieses Code-Snippet ruft die Benutzernamenzeichenfolge von ab HttpSession. Wenn die abgerufene Benutzernamenzeichenfolge null ist , wird die Webanwendung unterbrochen, indem der Kontrollfluss mit der Fehlermeldung "Sitzung beendet. Bitte anmelden." An die Anmeldeseite zurückgeleitet wird. Andernfalls ermöglicht die Webanwendung einen normalen Fluss durch den Rest der geschützten JSP-Seite, sodass der dynamische Inhalt dieser JSP-Seite bereitgestellt werden kann.

Ausführen von logoutSampleJSP1

Das Ausführen von logoutSampleJSP1 führt zu folgendem Verhalten:

  • Die Anwendung verhält sich korrekt durch den dynamischen Inhalt der geschützten JSP - Seiten zu verhindern home.jsp, secure1.jsp, secure2.jsp, und logout.jspaus bedient werden , wenn der Benutzer nicht angemeldet ist. Mit anderen Worten, vorausgesetzt , der Benutzer hat in aber Punkte , um den Browser zu denen JSP - Seiten URLs nicht angemeldet Die Webanwendung leitet den Kontrollfluss mit der Fehlermeldung "Sitzung wurde beendet. Bitte anmelden." an die Anmeldeseite weiter.
  • Ebenso kann die Anwendung verhält sich korrekt durch den dynamischen Inhalt der geschützten JSP - Seiten zu verhindern home.jsp, secure1.jsp, secure2.jsp, und logout.jspaus der Zeit nach dem Benutzer bedient wird bereits abgemeldet. Mit anderen Worten, nachdem der Benutzer sich bereits abgemeldet hat und den Browser auf die URLs dieser JSP-Seiten verweist, leitet die Webanwendung den Kontrollfluss mit der Fehlermeldung "Sitzung beendet. Bitte anmelden." An die Anmeldeseite weiter. ".
  • Die Anwendung verhält sich nicht korrekt, wenn der Benutzer, nachdem er sich bereits abgemeldet hat, auf die Schaltfläche Zurück klickt, um zu den vorherigen Seiten zurückzukehren. Die geschützten JSP-Seiten werden auch nach Beendigung der Sitzung wieder im Browser angezeigt (wenn sich der Benutzer abmeldet). Die kontinuierliche Auswahl eines Links auf diesen Seiten führt den Benutzer jedoch zur Anmeldeseite mit der Fehlermeldung "Sitzung beendet. Bitte anmelden.".

Verhindern Sie das Zwischenspeichern der Browser

Die Wurzel des Problems ist die Schaltfläche Zurück, die in den meisten modernen Browsern vorhanden ist. Wenn Sie auf die Schaltfläche Zurück klicken, fordert der Browser standardmäßig keine Seite vom Webserver an. Stattdessen lädt der Browser die Seite einfach aus dem Cache neu. Dieses Problem ist nicht auf Java-basierte Webanwendungen (JSP / Servlets / Struts) beschränkt. Es ist auch für alle Technologien gleich und betrifft PHP-basierte (Hypertext Preprocessor), ASP-basierte (Active Server Pages) und .NET-Webanwendungen.

Nachdem der Benutzer auf die Schaltfläche Zurück geklickt hat, findet kein Roundtrip zurück zu den Webservern (im Allgemeinen) oder den Anwendungsservern (im Fall von Java) statt. Die Interaktion erfolgt zwischen dem Benutzer, dem Browser und dem Cache. So auch mit der Anwesenheit von Code des Eintrag 3 in den geschützten JSP - Seiten wie home.jsp, secure1.jsp, secure2.jsp, und logout.jspdieser Code wird nie die Chance auszuführen , wenn die Zurück - Schaltfläche geklickt wird.

Depending on whom you ask, the caches that sit between the application servers and the browsers can either be a good thing or a bad thing. These caches do in fact offer a few advantages, but that's mostly for static HTML pages or pages that are graphic- or image-intensive. Web applications, on the other hand are more data-oriented. As data in a Web application is likely to change frequently, it is more important to display fresh data than save some response time by going to the cache and displaying stale or out-of-date information.

Fortunately, the HTTP "Expires" and "Cache-Control" headers offer the application servers a mechanism for controlling the browsers' and proxies' caches. The HTTP Expires header dictates to the proxies' caches when the page's "freshness" will expire. The HTTP Cache-Control header, which is new under the HTTP 1.1 Specification, contains attributes that instruct the browsers to prevent caching on any desired page in the Web application. When the Back button encounters such a page, the browser sends the HTTP request to the application server for a new copy of that page. The descriptions for necessary Cache-Control headers' directives follow:

  • no-cache: forces caches to obtain a new copy of the page from the origin server
  • no-store: directs caches not to store the page under any circumstance

For backward compatibility to HTTP 1.0, the Pragma:no-cache directive, which is equivalent to Cache-Control:no-cache in HTTP 1.1, can also be included in the header's response.

By leveraging the HTTP headers' cache directives, the second sample Web application, logoutSampleJSP2, that accompanies this article remedies logoutSampleJSP1. logoutSampleJSP2 differs from logoutSampleJSP1 in that Listing 4's code snippet is placed at the top of all protected JSP pages, such as home.jsp, secure1.jsp, secure2.jsp, and logout.jsp:

Listing 4

// ... response.setHeader ("Cache-Control", "no-cache"); // Erzwingt, dass Caches eine neue Kopie der Seite vom Ursprungsserver erhalten. Response.setHeader ("Cache-Control", "no-store"); // Weist die Caches an, die Seite unter keinen Umständen zu speichern. Response.setDateHeader ("Expires", 0); // Bewirkt, dass der Proxy-Cache die Seite als "veraltet" ansieht. Response.setHeader ("Pragma", "no-cache"); // HTTP 1.0-Abwärtskompatibilität String userName = (String) session.getAttribute ("User"); if (null == userName) {request.setAttribute ("Fehler", "Sitzung wurde beendet. Bitte anmelden."); RequestDispatcher rd = request.getRequestDispatcher ("login.jsp"); rd.forward (Anfrage, Antwort); } // ...