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 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
Was du baust und was es kostet
Also schreibst du den entscheidenden Teil eines Browsers selbst. Die Datei mini_browser.py bekommt eine kleine 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
Domainund 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 alsnullgeschrieben wird. Das Modell kennthttp- undhttps-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:
| Name | Seine Rolle in diesem Kurs |
|---|---|
https://reading.example | Die Leseliste: dein Dienst |
https://app.reading.example | Ein zweites Frontend für die Leseliste, das Kapitel 9 verwendet |
https://notes.reading.example | Ein Host, auf dem Leser eigene Seiten veröffentlichen können: der Nachbar |
https://other.example | Jede 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
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
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.