0%

Hoofdstuk 7

Toestand en identiteit

HTTP vergeet tussen verzoeken

Je leeslijst beantwoordt nu elke schrijfactie en elk oud pad, en je client en server zijn het over alles eens. Twee dingen ontbreken nog, en het is hetzelfde ding van twee kanten bekeken: de server kan de ene client niet van de andere onderscheiden, en daarom laat hij iedereen de wijzigen. Dit hoofdstuk geeft de server een manier om te weten wie er vraagt. Het begint met een feit over zelf.

Hier zijn twee verzoeken voor een pagina die “wie ben ik?” zou moeten beantwoorden, verstuurd door één programma met een seconde ertussen:

GET /me HTTP/1.1
Host: books.test

GET /me HTTP/1.1
Host: books.test

Ze zijn byte voor byte gelijk. Elk komt aan, krijgt zijn antwoord en wordt vergeten; onder HTTP/1.0 komt elk zelfs op een eigen verbinding aan. Niets in het tweede verzoek zegt dat het kwam van het programma dat het eerste stuurde. HTTP onthoudt niets tussen verzoeken: elk verzoek moet alles meedragen wat de server moet weten. Als de server een client moet herkennen, moet de client dus de herinnering meedragen, en die elke keer terugsturen.

Set-Cookie en Cookie

Die herinnering is een . De server deelt er een uit in een header Set-Cookie op een antwoord, en de client stuurt hem terug in een header Cookie op elk later verzoek aan die server:

Het antwoord op POST /login zet session=abc, en de client stuurt die terug met GET /me.

De client stuurt POST /login HTTP/1.1 met content-length: 0. De HTTP/1.0 200 OK van de server zet een cookie met de naam session en de abc: set-cookie: session=abc; HttpOnly; Max-Age=3600; Path=/. De client bewaart hem; zijn volgende verzoek, GET /me HTTP/1.1, draagt cookie: session=abc. Daaraan weet de server dat deze client heeft ingelogd, en hij antwoordt {"user": "ana"}.

De twee headers zijn geen spiegelbeelden. Een regel Set-Cookie draagt één naam en waarde, en daarna attributen gescheiden door ;, die de client vertellen hoe hij met de cookie moet omgaan. Een server die drie cookies zet, stuurt drie regels Set-Cookie. De regel Cookie draagt alleen namen en waarden, elke cookie die voor het verzoek geldt op één regel, gescheiden door ; , en nooit een attribuut: de attributen waren instructies voor de client, en de server weet al waar hij om vroeg.

De http.cookies van Python schrijft en leest beide. Een SimpleCookie werkt als een met cookies: je zet een waarde onder een naam, en zet daarna attributen op die entry alsof die ook een dict is. Dit blok draait in je browser, dus er wordt niets verstuurd. Voer het uit:

Try it

OutputString() is de waarde van een header Set-Cookie: session=abc; HttpOnly; Max-Age=3600; Path=/. output() zet er Set-Cookie: voor. De attributen komen er in alfabetische volgorde uit, in welke volgorde je ze ook zet, en hun namen mogen met willekeurige hoofdletters worden gezet. Alleen attributen die je zet, verschijnen; een onbekende naam zoals color werpt http.cookies.CookieError op.

Lezen gaat de andere kant op. Geef SimpleCookie de tekst van een header Cookie, en indexeer op naam:

Try it

Elke entry bewaart zijn tekst in .value, dus dit drukt abc-DEF_123 af, dan dark, dan 2: er kwamen twee cookies binnen op één regel.

Wat elk attribuut vraagt

Elk attribuut op een regel Set-Cookie is een verzoek aan de client:

  • Path=/ vraagt de client de cookie mee te sturen met verzoeken voor elk pad op de server. Een cookie die met Path=/books is gezet, zou alleen meegaan met verzoeken onder /books.

  • Max-Age=3600 vraagt de client de cookie 3600 seconden te bewaren en hem dan te vergeten. Max-Age=0 betekent “vergeet hem nu”, en zo verwijdert een server een cookie. Expires, een datum in plaats van een aantal seconden, is de oudere vorm van hetzelfde verzoek.

  • HttpOnly vraagt een browser om scripts in de pagina de cookie niet te laten lezen. De browser stuurt hem nog steeds met elk verzoek mee; hij verbergt hem alleen voor paginacode, zodat een script dat in een pagina is geïnjecteerd, hem niet kan kopiëren.

  • Secure vraagt de client de cookie alleen via HTTPS te sturen, nooit via gewone HTTP.

Je komt ook SameSite tegen, dat een browser vertelt of hij de cookie moet sturen als een andere site het verzoek start. Deze cursus heeft geen browser, dus paginascripts en SameSite komen niet aan bod.

Geen van deze kan door de server worden afgedwongen. De server stuurt het verzoek, en een nette client honoreert het; een slordige of vijandige client kan een cookie langer bewaren dan zijn Max-Age of hem overal naartoe sturen. De server moet alles wat ertoe doet aan zijn eigen kant beslissen, en dat is precies wat de volgende lessen doen.

Secure en deze cursus

Secure houdt een cookie weg van gewone HTTP, en versleutelt niets: iedereen die het netwerk kan lezen, kan een cookie lezen die via gewone HTTP wordt verstuurd. Alleen HTTPS, HTTP binnen een versleutelde -verbinding, verbergt hem. Hoofdstuk 9 laat die verpakking zien.

Je server spreekt gewone HTTP op het van deze machine. Als hij Secure zette, zou een correcte client de cookie bewaren en hem nooit terugsturen naar http://, en zou het inloggen niets lijken te doen. Daarom laat deze cursus Secure weg van de sessiecookie, als leeruitzondering voor een server die niemand anders kan bereiken. Een echte service achter HTTPS zet het wel.

Er komen twee GET /me-verzoeken binnen van één programma, met een seconde ertussen. Wat kan de server vertellen dat ze van dezelfde client kwamen?

Wat drukt dit af?

import http.cookies

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

Waartegen beschermt HttpOnly?

Waarom draagt de sessiecookie van deze cursus geen Secure?

Een cookie kan elke korte tekst bevatten, maar niets wat erin staat, is te vertrouwen: de client kan het naar believen veranderen. De volgende les houdt de echte feiten op de server, in een sessie, en geeft de client alleen een willekeurige id om terug te sturen.