Suche
Teil 12: Backend meets Frontend: Trainer for kids II

Log-in und Log-out einer Webanwendung mit Vaadin

Sven Ruppert

© Shutterstock / HelenField

Eine große Kleinigkeit bei jeder Webanwendung sind Log-in und Log-out. Außerdem sollte die Anwendung den Benutzer nicht einfach hinauswerfen, nur weil er den Reload-Knopf drückt. Wie das mit Vaadin funktioniert, zeigt dieser Teil von „Backend meets Frontend“.

Der vorherige Teil hat gezeigt, wie wir eine Komponente erstellen können. In unserem Fall war es die LoginComponent. Kurz beleuchtet haben wir die Themen, die im Bereich der Internationalisierung und dem Schreiben von Tests mittels jUnit5 und TestBench (Selenium) auf uns zukommen. Aber da ist noch eine Kleinigkeit…

Die Anmeldung

Wie jede (gute?) Anwendung, so hat auch unsere eine Anmeldung, die uns eine personalisierte Webanwendung ermöglicht. Der Log-in-Prozess selbst ist recht simpel. Der Benutzer muss die beiden Werte für Login und Password eingeben. Die Nachfolgende Validierung führt dazu, dass entweder der Log-in wieder angezeigt wird, wenn er nicht erfolgreich war, oder die Anwendung zeigt uns den lang ersehnten Inhalt der Anwendung, mit der wir arbeiten möchten. Aber dann passiert es. Der Benutzer drückt nach der Anmeldung den Reload Button. Was dann passiert sehen wir in Abbildung 1.

Abb. 1: Bei einem Reload wird der Benutzer zur Log-in-Seite zurückgeworfen

Das ist sicherlich nicht das, was wir erreichen möchten. Sicherlich wird es Fälle geben, bei denen genau dieses Verhalten nach einem Log-in gewünscht ist, hier aber nicht. Soll es doch für Kinder in der Handhabung möglichst einfach  sein. Ziel ist es also, nach dem Log-in-Prozess, solange wir nicht die Abmeldung durchlaufen haben, bei einem Reload der Seite, dort zu verbleiben, wo wir zu diesem Zeitpunkt innerhalb der Vaadin-Web-App gewesen sind. Nur wenn der Abmeldeprozess gestartet worden ist, soll ein Reload dazu führen, dass die Anmeldemaske wieder gezeigt wird.

Um dieses Verhalten zu realisieren, müssen wir uns zuerst ein wenig genauer ansehen, was passiert, wenn der Benutzer ein erneutes Laden der Seite provoziert. Hierzu werden wir die Anwendung mit einigen System.out.println-Anweisungen versehen. Mir ist bewusst, das die Verwendung von System.out.println nicht optimal ist und in produktiv eingesetzten Systemen nichts zu suchen hat. Auch Nebenläufigkeiten werden hier nicht betrachtet, da wir nur mit einem einzigen User auf der Instanz arbeiten.

Zur Erinnerung, die Methode println ist synchronized.

    public void println(String x) {
        synchronized (this) {
            print(x);
            newLine();
        }
    }

Wenn wir nun in der Methode protected void init(VaadinRequest request) in der Klasse MainUI eine kleine Info einbauen, um zu sehen wie oft diese Methode eigentlich aufgerufen wird, sehen wir, dass dies bei jedem Reload der Fall ist. Das bedeutet, dass mit jedem Aufruf eine neue Instanz der UI aufgebaut wird. Aber gehen wir erst ein paar Schritte zurück. Nach dem erfolgreichen Anmeldevorgang soll in der Session eine Instanz der Klasse User abgelegt werden, um dort ein paar Informationen direkt verfügbar zu haben, zum Beispiel den Namen des Benutzers.

public class User implements Serializable {

  private String login;
  private String foreName;
  private String familyName;
  //SNIPP

Ein möglicher Zeitpunkt dafür ist direkt nach der Validierung der Eingabedaten in das Anmeldeformular. Dazu erweitern wir den ClickListener unsere OK-Buttons.

ok.addClickListener((Button.ClickListener) event -> {
      boolean isValid = loginService.check(login.getValue(), 
                                           password.getValue());
      clearInputFields();
      
      if (isValid) {
        UI.getCurrent()
            .getSession()
              .setAttribute(
                  SESSION_ATTRIBUTE_USER, 
                  loginService.loadUser(login.getValue()));
      }
      UI.getCurrent()
        .setContent((isValid) ? mainViewSupplier.get() : this);
    });

Nach der Überprüfung der Anmeldedaten wird der LogService verwendet, um eine Instanz der Klasse User zu erzeugen, basierend auf den Daten, die zur Validierung verwendet worden sind. Diese Instanz wird dann in der Session unter einer Konstanten SESSION_ATTRIBUTE_USER abgelegt. Der Rest ist unverändert und setzt eine Instanz der Klasse Mainview im Falle einer erfolgreichen Anmeldung. Ansonsten bleiben wir im Anmeldeprozess. Wenn wir das ausprobieren, müssen wir leider feststellen, dass sich nichts verändert hat. Hierzu müssen wir in der MainUI in der init-Methode eine Entscheidung treffen. Wir prüfen, ob es eine Instanz User in der Session gibt. Wenn dem so ist, lassen wir alles wie gehabt.

  @Override
  protected void init(VaadinRequest request) {
    System.out.println("init - request = " + request);
    if(! user().isPresent()) setContent(login());
    setSizeFull();
  }
  
  private Optional<User> user() {
    return Optional
        .ofNullable(
            getCurrent()
                .getSession()
                .getAttribute(User.class));
  }

Wenn wir dies ausprobieren, müssen wir leider feststellen, dass auch dies nicht reicht. Mit jeden Reload der Seite erscheint wieder der Anmeldevorgang. Die Lösung ist beim Verhalten des Frameworks zu finden. Wenn wir die Instanz der Klasse MainUI bei einem Reload behalten möchten, annotieren wir die Klasse mit @PreserveOnRefresh. Nun wird bei einem Reload nicht automatisch die Instanz der UI verworfen und damit auch nicht die Session. Die init-Methode wird ebenfalls nicht mehr durchlaufen. Stattdessen tritt die Methode protected void refresh(VaadinRequest request) in Aktion. Hier könnten wir bei einem Reload der Seite eingreifen, wenn Bedarf besteht.

Abb. 2: Nun landet der Benutzer nach dem Reload dort, wo er vorher auch war

Backend meets Frontend

In der Artikelserie Backend meets Frontend stellt Sven Ruppert (Vaadin) Konzepte und Technologien rund um das UI-Framework Vaadin vor. Sein Fokus liegt dabei auf modernem Web-Design für Java-Backend-Entwickler.

Zum ersten Teil und damit dem Start der Tutorien rund um die UI-Entwicklung mit Java geht es hier entlang. Alle Teile der Serie Backend meets Frontend finden sich hier.

Abmelden von der App

Nun können wir uns anmelden. Wie aber sieht es mit dem Abmelden aus? Der Abmeldevorgang wird gestartet, indem der Button Logout betätigt wird. Innerhalb des ClickListeners wird dann folgendes durchgeführt.

UI ui = getUI();
VaadinSession vaadinSession = ui.getSession();
vaadinSession.setAttribute(SESSION_ATTRIBUTE_USER, null);
vaadinSession.close();
ui.getPage().setLocation("/");

In der Session wird die Instanz der Klasse User auf null gesetzt und dann die Session geschlossen. Damit der Benutzer nicht auf der gerade angezeigten Seite verbleibt, wird er auf die Startseite weitergeleitet. Dort beginnt dann wieder der Anmeldevorgang.

Damit das alles ein wenig ansprechender aussieht, verwenden wir einen Dialog, bei dem der Benutzer nochmals die Chance hat, den Abmeldevorgang abzubrechen. Hierzu verwenden wir das erste Mal ein Add-on. Genauer gesagt das Add-on ConfirmDialog. Das ist auf der Vaadin-Seite im Bereich der Open-Soure-Add-ons zu finden. Wer einen Blick in die Quelltexte werfen möchte, kann das auf GitHub tun. Um das Add-on in unserem Projekt zu verwenden, fügen wir in der pom.xml die Abhängigkeit des Add-ons hinzu.

    <dependency>
      <groupId>org.vaadin.addon</groupId>
      <artifactId>confirmdialog</artifactId>
      <version>3.2.0</version>
    </dependency>

Nun benötigen wir noch den Button selbst. Hier wird bei einem Klick der Dialog erzeugt und angezeigt. Wird der OK-Button dieses Dialogs gedrückt, erfolgt der Log-out selbst.

    final Button button
        = new Button(caption,
                     (e) -> {
                       UI ui = UI.getCurrent();
                       ConfirmDialog.show(
                           ui,
                           message, 
                           (ConfirmDialog.Listener) dialog -> {
                             if (dialog.isConfirmed()) {
                               VaadinSession vaadinSession = ui.getSession();
                               vaadinSession.setAttribute(SESSION_ATTRIBUTE_USER, null);
                               vaadinSession.close();
                               ui.getPage().setLocation("/");
                             }
                             else {
                               // User did not confirm
                               // CANCEL STUFF
                             }
                           });
                     });

Alles zusammen sieht nun wie in Abbildung 3 aus.

Abb. 3: Der Benutzer kann sich sowohl an- als auch abmelden

Fazit

In diesem Teil habem wir uns angesehen, wie wir mit dem Reload einer Vaadin-Web-App umgehen können. Ziel war es, dass ein Anmeldevorgang eine Verwendung des Reload-Buttons überlebt, um es für den Anwender komfortabler zu machen. Mit dem Wissen, wann und wo wir das Verhalten beeinflussen können, stehen uns nun die Möglichkeit offen Frameworks wie Apache Shiro einzubauen.

Den Quelltext findet ihr auf GitHub. Bei Fragen und Anregungen einfach melden unter sven@vaadin.com oder per Twitter @SvenRuppert.

Happy Coding!

Geschrieben von
Sven Ruppert
Sven Ruppert
Sven Ruppert arbeitet seit 1996 mit Java und ist Developer Advocate bei Vaadin. In seiner Freizeit spricht er auf internationalen und nationalen Konferenzen, schreibt für IT-Magazine und für Tech-Portale. Twitter: @SvenRuppert
Kommentare
  1. Dr. Eck-Spatz2017-09-19 11:47:25

    Schönes Beispiel an dem man sieht wie nutzlos Vaadin ist. Folgendes muss man sich mal auf der Zunge zergehen lassen :

    "... Ziel war es, dass ein Anmeldevorgang eine Verwendung des Reload-Buttons überlebt..."

    Weiterhin wird hier ein Login-Logout (was anderswo ein 3-Zeiler ist) gefeiert, dass es schon fast peinlich ist.

    Vaadin ist tot, bzw. hat nie gelebt.

  2. Daniel Nordhoff-Vergien2017-09-24 10:39:35

    Ist es beim Logout notwendig die VaadinSession zu säubern, also in dem Beispiel den Nutzer auf 'null' zu setzen? Reicht das folgende 'close()' nicht aus?

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.