0%

Kapitel 7

Zustand und Identität

HTTP vergisst zwischen Anfragen

Deine Leseliste beantwortet jetzt jeden Schreibzugriff und jeden alten Pfad, und dein Client und dein Server sind sich über all das einig. Zwei Dinge fehlen noch, und es ist dasselbe Ding von zwei Seiten gesehen: Der Server kann einen Client nicht von einem anderen unterscheiden, und deshalb lässt er jeden die ändern. Dieses Kapitel gibt dem Server eine Möglichkeit zu wissen, wer fragt. Es beginnt mit einer Tatsache über selbst.

Hier sind zwei Anfragen an eine Seite, die „wer bin ich?“ beantworten soll, gesendet von einem Programm im Abstand von einer Sekunde:

GET /me HTTP/1.1
Host: books.test

GET /me HTTP/1.1
Host: books.test

Sie sind Byte für Byte gleich. Jede kommt an, bekommt ihre Antwort und wird vergessen; unter HTTP/1.0 kommt jede sogar auf einer eigenen Verbindung an. Nichts in der zweiten Anfrage sagt, dass sie von dem Programm kam, das die erste gesendet hat. HTTP behält zwischen Anfragen kein Gedächtnis: Jede Anfrage muss alles mitbringen, was der Server wissen muss. Wenn der Server einen Client wiedererkennen soll, muss also der Client die Erinnerung tragen und sie jedes Mal zurücksenden.

Set-Cookie und Cookie

Diese Erinnerung ist ein . Der Server gibt einen in einem Header Set-Cookie auf einer Antwort heraus, und der Client sendet ihn in einem Header Cookie bei jeder späteren Anfrage an diesen Server zurück:

Die Antwort auf POST /login setzt session=abc, und der Client sendet es mit GET /me zurück.

Der Client sendet POST /login HTTP/1.1 mit content-length: 0. Das HTTP/1.0 200 OK des Servers setzt ein Cookie namens session mit dem abc: set-cookie: session=abc; HttpOnly; Max-Age=3600; Path=/. Der Client behält es; seine nächste Anfrage, GET /me HTTP/1.1, trägt cookie: session=abc. Daran erkennt der Server, dass sich dieser Client angemeldet hat, und antwortet {"user": "ana"}.

Die beiden Header sind keine Spiegelbilder. Eine Zeile Set-Cookie trägt einen Namen und einen Wert, dann durch ; getrennte Attribute, die dem Client sagen, wie er das Cookie behandeln soll. Ein Server, der drei Cookies setzt, sendet drei Zeilen Set-Cookie. Die Zeile Cookie trägt nur Namen und Werte, jedes Cookie, das für die Anfrage gilt, auf einer Zeile, getrennt durch ; , und nie ein Attribut: Die Attribute waren Anweisungen für den Client, und der Server weiß schon, was er verlangt hat.

Pythons http.cookies schreibt und liest beide. Ein SimpleCookie funktioniert wie ein von Cookies: Du setzt einen Wert unter einem Namen und setzt dann Attribute auf diesem Eintrag, als wäre er auch ein Dictionary. Dieser Block läuft in deinem Browser, also wird nichts gesendet. Führ ihn aus:

Try it

OutputString() ist der Wert eines Headers Set-Cookie: session=abc; HttpOnly; Max-Age=3600; Path=/. output() setzt Set-Cookie: davor. Die Attribute kommen in alphabetischer Reihenfolge heraus, egal in welcher Reihenfolge du sie gesetzt hast, und ihre Namen können in beliebiger Groß- und Kleinschreibung gesetzt werden. Nur Attribute, die du gesetzt hast, erscheinen; ein unbekannter Name wie color löst http.cookies.CookieError aus.

Lesen geht in die andere Richtung. Gib SimpleCookie den Text eines Headers Cookie und greif per Name darauf zu:

Try it

Jeder Eintrag hält seinen Text in .value, also gibt das abc-DEF_123 aus, dann dark, dann 2: Zwei Cookies kamen auf einer Zeile an.

Worum jedes Attribut bittet

Jedes Attribut auf einer Zeile Set-Cookie ist eine Bitte an den Client:

  • Path=/ bittet den Client, das Cookie bei Anfragen für jeden Pfad auf dem Server mitzusenden. Ein mit Path=/books gesetztes Cookie würde nur bei Anfragen unter /books mitgehen.

  • Max-Age=3600 bittet den Client, das Cookie 3600 Sekunden lang zu behalten und es dann zu vergessen. Max-Age=0 bedeutet „vergiss es jetzt“, und so löscht ein Server ein Cookie. Expires, ein Datum statt einer Anzahl von Sekunden, ist die ältere Form derselben Bitte.

  • HttpOnly bittet einen Browser, Skripte auf der Seite das Cookie nicht lesen zu lassen. Der Browser sendet es weiterhin bei jeder Anfrage; er verbirgt es nur vor dem Code der Seite, damit ein in eine Seite eingeschleustes Skript es nicht kopieren kann.

  • Secure bittet den Client, das Cookie nur über HTTPS zu senden, nie über einfaches HTTP.

Du wirst auch SameSite begegnen, das einem Browser sagt, ob er das Cookie senden soll, wenn eine andere Website die Anfrage auslöst. Dieser Kurs hat keinen Browser, also werden weder Seitenskripte noch SameSite gezeigt.

Keins davon kann der Server erzwingen. Der Server äußert die Bitte, und ein ordentlicher Client hält sich daran; ein nachlässiger oder feindseliger Client kann ein Cookie über sein Max-Age hinaus behalten oder es überallhin senden. Der Server muss alles, was zählt, auf seiner eigenen Seite entscheiden, und genau das tun die nächsten Lektionen.

Secure und dieser Kurs

Secure hält ein Cookie von einfachem HTTP fern, und es verschlüsselt nichts: Jeder, der das Netzwerk mitlesen kann, kann ein über einfaches HTTP gesendetes Cookie lesen. Nur HTTPS, HTTP innerhalb einer verschlüsselten -Verbindung, verbirgt es. Kapitel 9 zeigt diese Hülle.

Dein Server spricht einfaches HTTP auf der dieses Rechners. Wenn er Secure setzen würde, würde ein korrekter Client das Cookie behalten und nie an http:// zurücksenden, und die Anmeldung schiene nichts zu bewirken. Deshalb lässt dieser Kurs Secure beim Session-Cookie weg, als zum Lernen für einen Server, den sonst niemand erreichen kann. Ein echter Dienst hinter HTTPS setzt es.

Zwei Anfragen GET /me kommen von einem Programm im Abstand von einer Sekunde an. Was kann dem Server sagen, dass sie vom selben Client kamen?

Was gibt das aus?

import http.cookies

cookie = http.cookies.SimpleCookie()
cookie["session"] = "abc"
cookie["session"]["httponly"] = True
cookie["session"]["path"] = "/"
print(cookie["session"].OutputString())

Wovor schützt HttpOnly?

Warum trägt das Session-Cookie dieses Kurses kein Secure?

Ein Cookie kann jeden kurzen Text enthalten, aber nichts darin ist vertrauenswürdig: Der Client kann es nach Belieben ändern. Die nächste Lektion behält die echten Fakten auf dem Server, in einer Session, und gibt dem Client nur eine zufällige ID zum Zurücksenden.