Die Vergesslichkeit des Servers - Sessions

Die Kommunikation über HTTP hat einen entscheidenden Nachteil: Ohne zusätzliche Bemühungen vergisst der Webserver nach jedem Request, wer ihn da eben besucht hat. Man spricht auch von einer zustandslosen Verbindungen zwischen Client und Server (Quelle).

Dieser Missstand ist für einige Webanwendungen vollkommen unvorteilhaft. Denn wenn man weiß, dass z.B. bei einem Webshop viele Seitenwechsel notwendig sind, um Artikel in den Warenkorb zu legen, die Adresse und Zahlungsdaten anzugeben und am Ende den Kauf abzuschließen, dann muss der Server sich merken können, wer da shoppt.

Zu diesem Zweck gibt es unter anderem ein technisches Konzept, das mit dem Begriff Sessions überschrieben ist. Eine Session ist eine Sitzung von Client und Server, die in der Regel mit dem ersten Request beginnt und bis zum Schließen des Browserfensters dauert. Dabei schenkt der Server dem Client einen Keks, einen Session-Cookie, den man auch mit den Entwicklertools in Chrome finden kann.

Mit dem Session-Cookie identifiziert sich der Client bei jedem erneuten Request gegenüber dem Server. Der Server kann also wiedererkennen, wer da kommt und quasi eine Beziehung zum Client aufbauen. Jeder Client erhält seine eigene Session. Mit diesem Konzept lassen sich z.B. Logins bauen, Sprachschalter realisieren und Formulartunnel erstellen.

Sessions mit Flask

Die Dokumentation von Flask zum Thema Sessions zeigt ein funktionierendes Beispiel, das wir zunächst besprechen wollen.

from flask import Flask, session, redirect, url_for, escape, request

app = Flask(__name__)

@app.route('/')
def index():
    if 'username' in session:
        return 'Logged in as %s' % escape(session['username'])
    return 'You are not logged in'

@app.route('/login', methods=['GET', 'POST'])
def login():
    if request.method == 'POST':
        session['username'] = request.form['username']
        return redirect(url_for('index'))
    return '''
        <form action="" method="post">
            <p><input type=text name=username>
            <p><input type=submit value=Login>
        </form>
    '''

@app.route('/logout')
def logout():
    # remove the username from the session if it's there
    session.pop('username', None)
    return redirect(url_for('index'))

# set the secret key.  keep this really secret:
app.secret_key = 'A0Zr98j/3yX R~XHH!jmN]LWX/,?RT'

Das Beispiel definiert drei Routen, die die Minimalbedingungen für eine Website mit Userlogin implementieren: Startseite, Login und Logout.

Der Aufruf der Startseite zeigt an, dass der User noch nicht eingeloggt ist. In den Entwicklertools findet sich auch kein Cookie (vgl. Abb.).

Noch kein Cookie gesetzt
Abbildung 1: Noch kein Cookie gesetzt

Die manuelle Eingabe von /login zeigt ein minimalistisches Loginformular, das jeden beliebigen Inhalt aufnimmt. Für dieses Beispiel ist das ok, allerdings sind Formulare, die keine Validierung und Säuberung von Eingabedaten durchlaufen, extrem gefährlich für die Gesundheit des Servers!

Ein Cookie wird es gesetzt, nachdem das Formular abgeschickt wurde (vgl. Abb.).

Ein Session-Cookie wurde gesetzt
Abbildung 2: Ein Session-Cookie wurde gesetzt

Wichtig zu bemerken ist, dass die Route /login die Userin nach erfolgreichem Einloggen wieder auf die Homepage schickt. Dort steht, wer man ist. Aktualisiert man die Seite, bleibt die Information stehen, obwohl der Client erneut einen Request zum Server geschickt hat. Der Server hat nicht vergessen, wer wir sind! Das Problem ist gelöst. Aber warum?

Daten in der Session speichern

Sessions können Daten speichern. Das heißt, für jede Sitzung und damit für jeden User können individuelle Daten gespeichert werden. Der entscheidende Teil im obigen Code ist der folgende:

if request.method == 'POST':
    session['username'] = request.form['username']
    return redirect(url_for('index'))

Hier wird geschaut, ob das Formular abgeschickt wurde, was man über die verwendete HTTP-Methode feststellen kann. Wenn ja, wird der eingegebene Wert aus dem Formular gespeichert. Dabei wird der Session-Speicherplatz in username mit dem Wert in username aus dem Formular belegt. Flask verwaltet Sessiondaten in einem so genannten dictionary, einem sehr häufig verwendeten Python-Datentyp. Hierzu empfiehlt sich, den entsprechenden Abschnitt in der Python-Dokumentation zu lesen oder aber einen guten Tutorialausschnitt auf Deutsch. Das dictionary hat in unserem Beispiel die folgende Gestalt:

{
  'username': u'Peter'
}

Bei jedem Request der Homepage wird anschließend nachgeschaut, ob der Speicherplatz username belegt ist. Damit lässt sich feststellen, ob die Session existiert, also jemand eingeloggt ist, und wie die Person heißt.

@app.route('/')
def index():
    if 'username' in session:
        return 'Logged in as %s' % escape(session['username'])
    return 'You are not logged in'

Daten aus der Session löschen

Sobald der User sich über die Route /logout abmeldet, wird der Speicherplatz in der Session wieder freigegeben.

@app.route('/logout')
def logout():
    # remove the username from the session if it's there
    session.pop('username', None)
    return redirect(url_for('index'))

Anschließend gelangt die Userin wieder auf die Homepage und der Cookie ist auch in den Entwicklertools nicht mehr sichtbar.

Ein Wort zur Sicherheit

Das Beispiel hier zeigt nur das, was es braucht, damit man Sessions erklären kann. Für ein sicheres Konzept der Benutzerauthentifizierung und -authorisierung gibt es noch einiges zu beachten, das an dieser Stelle nicht besprochen werden kann. Es findet sich aber viel zum Thema Sicherheit und Sessions in der Literatur.

Session-Cookies sollten auf jeden Fall verschlüsselt werden, denn wer sie auslesen und manipulieren kann, kann die Identität eines anderen Users annehmen! Daher wird in der letzten Zeile des obigen Beispielcode ein secret festgelegt, mit dem alle Session-Cookies verschlüsselt werden, wenn sie an den Client geschickt werden. Zeigt ein Client beim Request dem Server seinen Cookie vor, wird das secret zum Entschlüsseln verwendet.

Anregungen, damit keine Langeweile aufkommt

  • Übertragen Sie dieses Konzept sinnvoll auf Ihr eigenes Projekt!
  • Realisieren Sie ein Login in Ihrem Projekt. Eingeloggte Benutzer_innen sehen mehr/andere Inhalte, als anonyme.
  • Wie könnte eine simple Benutzerverwaltung aussehen, mit der geprüft werden kann, ob man sich anmelden darf oder nicht?
  • Wie lässt sich eine Benutzerregistrierung realisieren?
  • Recherchieren Sie zum Thema "Formularvalidierung"!