0%

Kapitel 8

Origins und Cross-Site-Anfragen

Der Browser, den du gleich modellierst

Jeder Client, den du bisher auf deinen Server gerichtet hast, tut genau, was man ihm sagt. wire.py sendet die Bytes, die du tippst. pl_sec.py sendet das , das man ihm gibt. Ein Test baut ein Anfrage- und ruft handle() auf.

Ein Browser ist eine andere Art von Client. Er entscheidet selbst, welche Cookies mit einer Anfrage mitgehen. Er entscheidet, ob eine Anfrage überhaupt gesendet wird. Und er entscheidet, ob die Seite, die eine Anfrage gestartet hat, die Antwort lesen darf. Ein Mensch öffnet eine Seite oder klickt auf eine Schaltfläche, und der Browser trifft diese drei Entscheidungen, ohne jemanden zu fragen.

Dein Dienst verlässt sich bereits auf diese Entscheidungen. In Kapitel 4 hast du Secure, SameSite=Lax und das Präfix __Host- an das Session-Cookie gesetzt, und jedes davon ist eine Anweisung an einen Browser. In Kapitel 7 hat der Dienst eine Seite mit einer Delete-Schaltfläche an jedem Buch bekommen. Das Einzige, was ein Löschen von dieser Seite autorisiert, ist das Session-Cookie, und das hängt ein Browser nach seinen eigenen Regeln an.

Dieses Kapitel und das nächste handeln von diesen Regeln. Es gibt ein Hindernis: Diese Website hat keinen Browser. Dein Arbeitsbereich ist ein und ein Editor, und nichts darin kann eine Seite laden.

Was du baust und was es kostet

Also schreibst du den entscheidenden Teil eines Browsers selbst. Die Datei mini_browser.py bekommt eine kleine pro Regel: was die einer ist, welche Cookies mit einer Anfrage mitgehen und, in Kapitel 9, ob eine Seite eine Antwort lesen darf. Eine mitgelieferte Funktion, visit(), ruft deine Regeln für eine einzelne Anfrage nacheinander auf, und deine Tests tragen ihre Anfragen im Speicher zu handle().

Das ist ein Modell, und ein Modell hat seinen Preis. Ein echter Browser folgt dem Fetch-Standard, dem HTML-Standard und dem Cookie-Standard, RFC 6265bis. Zusammen umfassen sie Tausende Zeilen. Deine Funktionen kommen auf ein paar Dutzend. Jede Regel, die du schreibst, beruht auf einem dieser Standards, und jede Lektion sagt, was ihre Regel weglässt oder vereinfacht. Hier sind die größten Lücken, noch vor jedem Code:

  • Keine Public Suffix List. Ein echter Browser entscheidet mit dieser , welche Hosts als eine Site zusammengehören. Das Modell verwendet eine kürzere Regel, und Lektion 2 erklärt beide.

  • Kein Attribut Domain und kein Ablauf. Ein Cookie gehört im Modell zu genau einem Host, und es bleibt, bis der Server es entfernt.

  • Keine Frames, keine , keine Caches. Das Modell betrachtet jeweils eine Seite und eine Anfrage.

  • Keine null-Origins. Ein echter Browser gibt manchen Seiten eine Origin, die als null geschrieben wird. Das Modell kennt http- und https-URLs und lehnt alles andere ab.

  • Zwei feinere Regeln über Header, die Kapitel 9 nennt, wenn es dort ankommt.

  • Keine Unterschiede zwischen Browsern. Das Modell gibt auf jede Frage eine Antwort. Wo echte Browser verschiedene Antworten geben, sagt die Lektion es.

Behalte noch eine Grenze im Kopf. Wenn ein Test in diesen Kapiteln besteht, hat dein Server gegen das Modell bestanden. Nichts auf dieser Website lässt ihn gegen einen echten Browser laufen.

Was bedeutet es, dass der Browser in diesem Kurs ein Modell ist?

Vier Namen

Eine Browserregel vergleicht, woher eine Seite kam, mit dem Ziel einer Anfrage. Das Modell braucht also ein paar Orte, und der Kurs verwendet von hier bis zum Abschlussprojekt dieselben vier:

NameSeine Rolle in diesem Kurs
https://reading.exampleDie Leseliste: dein Dienst
https://app.reading.exampleEin zweites Frontend für die Leseliste, das Kapitel 9 verwendet
https://notes.reading.exampleEin Host, auf dem Leser eigene Seiten veröffentlichen können: der Nachbar
https://other.exampleJede andere Site

Alle vier enden auf .example, eine Endung, die für Beispiele reserviert ist und nie zu einem echten Server führt. Nichts in diesem Kurs schlägt diese Namen nach. Im Modell sind sie Text, den deine Funktionen zerlegen und vergleichen.

Eine Origin

Das Erste, was ein Browser über eine URL ermittelt, ist ihre Origin: das Schema, der Host und der . Nichts anderes in der URL zählt.

https://reading.example:8443/books?status=done
|___|   |_____________| |__||________________|
scheme       host       port  path and query

|__________________________|
         the origin

In https://reading.example:8443/books?status=done ist das Schema https, der Host reading.example und der Port 8443. Diese drei bilden die Origin, die als https://reading.example:8443 geschrieben wird. Der Pfad /books und der Query- status=done gehören nicht dazu.

Die meisten URLs nennen keinen Port. urlsplit aus with Python zeigt, was da ist und was nicht. Führe diesen Block aus:

Try it

Die erste Ausgabezeile endet auf None, die letzte ebenfalls: urlsplit meldet keinen Port, wenn die URL keinen nennt. Die Origin-Regel füllt die Lücke. Ein fehlender Port bedeutet den Standardport des Schemas, 443 für https und 80 für http. Die ersten beiden URLs haben also dieselbe Origin, und sie wird ohne Port geschrieben: https://reading.example. Die dritte nennt einen anderen Port und ist eine andere Origin. Die vierte hat ein anderes Schema und ist ebenfalls eine andere Origin.

Zwei URLs teilen sich nur dann eine Origin, wenn alle drei Teile übereinstimmen.

Welches Paar teilt sich eine Origin?

Welche zwei Zeilen gibt dieser Code aus?

from urllib.parse import urlsplit

for url in ["http://reading.example:8080/x", "https://app.reading.example/books"]:
    parts = urlsplit(url)
    print(parts.scheme, parts.hostname, parts.port)

Eine Origin ist die Einheit, die ein Browser schützt, und Lektion 4 sagt, was dieser Schutz umfasst. In dieser Lektion wurde nichts irgendwohin gesendet. In der nächsten schreibst du die Origin als Funktion und fügst eine gröbere Einheit hinzu, nach der sich Cookies richten.