Cookies
Når man programmerer webapplikationer, kræver det ofte en styring af brugerne. Webapplikationen skal holde styr på, hvilke brugere der udfører hvilke handlinger fra de har logget på systemet, til deres session er afsluttet. Vi har tidligere på PC World Online skrevet om brugerstyring. I denne artikel gennemgår vi konkret, hvorledes brugerstyring implementeres i et scripting-miljø som for eksempel ASP eller PHP.
Serveren skal vide, hvem der sender hvad i hvilken situation, for at kunne give eksakt respons på brugeren forespørgsel. Men den succesfulde web-teknologi er ikke konstrueret til denne situation, hvilket betyder at programmørerne må finde teknikker til at løse opgaven.
World Wide Web er et tilstandsløst miljø. Det betyder, at serveren som udgangspunkt er ligeglad med, hvem klienten er. Den oprindelige ide med webbet var blot, at man skulle kunne browse - bladre - gennem mange sider af videnskabelig information i form af tekst og billeder. Senere hen er der lagt et lag oven på, som gør det muligt for udvikleren at holde styre på brugerflokken.
Mulighederne for brugerstyring er: HTTP-brugeridentifikation (Basic authentication), brugerstyring med cookies samt session-variabler. I det følgende kigger vi på de tre muligheder.
HTTP-brugeridentifikation
Ved HTTP-brugeridentifikation (Basic authentication), sættes webserveren op til at betragte en del af webstedet som beskyttet. Man skal definere et realm - et rige - hvor brugeren må befinde sig. Hvis brugeren bevæger sig fra et realm til et andet, skal hun igen indtaste brugernavn og password. HTTP-brugeridentifikation er en basal brugerstyring, som ikke gør så meget andet en at give privilegier til et særlig udvalgt gruppe. Det kan være særdeles anvendeligt i situationer hvor man kan køre identifikationen op imod en eksisterende brugeradministration. Det kan være en brugeradministration i intranet baseret på Windows, hvor brugernes eksisterende NT-logins anvendes umiddelbart i web-miljøet. I scripting-miljøer som PHP og ASP kan du bruge scripts til at se, om brugeren er logget på via HTTP-brugeridentifikation.
Men i webapplikationer, der er rettet mod alle mulige brugere benyttes HTTP-brugeridentifikation kun sjældent. Derfor vil vi ikke kigge yderligere på denne teknik.
Brugerstyring med cookies
Cookies blev opfundet af Netscape for netop at kunne håndtere tilstanden i kommunikationen mellem server og klienterne. Cookies er ganske simple i opbygningen. De er små tekststumper på maksimalt 4 kilobyte, som bliver genereret på serveren. Serveren sender dem til browseren, der så gemmer cookierne midlertidigt på brugerens harddisk. Næste gang brugerens browser henter en side fra web-domænet, hvor cookien blev udstedt fra, da sender browseren så samme cookie tilbage til serveren. Ved at gemme en brugernøgle i cookien kan et program på serveren genkende browserne, som henvender sig igen - og dermed holde styr på hver enkelt bruger. Vi gennemgår principperne senere i artiklen.
Session-variabler
Session-variabler
De fleste scripting-sprog implementerer en variabeltype, der kaldes for en session-variabel. Før vi kan bruge den, skal den forklares: Et serverscript bliver udført som et enkeltstående program. Når scriptet starter, er den eneste information, som det har til rådighed, de informationer, som browseren har sendt i sin forespørgsel til serveren. Browseren kan for eksempel have sendt formular-værdier, der kaldes Query Strings, forespørgselsstrenge. Det er navnet for det, der kan stå efter et spørgsmålstegn i webadresser.
I adressen http://www.pcworld.dk/Default.asp?Mode=2&ArtikelID=2106 er QueryStringen således "?ArtikelID=2106&Mode=2". Denne forespørgselsstreng fortæller scriptet på www.pcworld.dk, at artikel nummer 2106 skal hentes fra databasen, som ligger bagved webserveren.
Når scriptet er afsluttet, og den genererede webside er sendt tilbage til browseren, er al informationen væk. I forbindelse med de fleste webapplikationer er det ikke særligt smart. Der er jo en masse ting, udvikleren skal vide om brugeren. Ved login af brugere med identifikation, skal senere scripts vide, om brugeren nu også er logget ind eller ej.
En udbredt fejl i pionertiden var, at udvikleren fastholdt brugernes identitet og anden tilstandsinformation, ved blot at kaste det hele tilbage til browseren som skjulte felter i en formular. Men den går ikke.
Vi kunne for eksempel forestille os, at en bruger logger ind på et web-sted ved at skrive sit brugernavn og password. Derefter kunne vi sende en webside tilbage til browseren, hvor denne information står i skjulte felter. Det gøres sådan her:
<FORM ACTION="http://minserver/nytscript">
...
<INPUT TYPE=HIDDEN NAME="loginstatus"
VALUE="er logget ind">
<INPUT TYPE=HIDDEN NAME="brugernavn"
VALUE="bruger1572">
...
<INPUT TYPE=SUBMIT>
</FORM>
Tilsyneladende ville det løse vores problem: Når brugeren afsender formularen - for eksempel en e-kunde, der lægger noget i sin indkøbskurv - får vi både brugernavnet, samt information om, at brugeren havde været igennem login-proceduren.
Her er den oplagte og superfarlige sikkerhedsbrist, at enhver kan kopiere ovenstående ind i et HTML-dokument på sin harddisk, og så sende det afsted til serveren. På den måde skal man kun kende brugernavnet for at hacke systemet.
Hvor besynderligt det end kan lyde, ser man faktisk stadig denne og lignende typer fejl dukke op igen og igen.
Vi er altså nødt til at gemme nogle værdier, som kun ligger på serveren, så ingen kan se dem. Vi skal også kunne overføre informationerne fra script til script, hver gang den samme bruger laver en forespørgsel.
Unikke nøgler
Den smarte læser har selvfølgelig regnet ud, at man må kunne konstruere et eller andet med cookies her. Den rigtigt gode nyhed er, at i scriptingsprog som ASP og PHP er hele mekanikken allerede til rådighed for udvikleren. Det er de såkaldte session-variabler. I en session-variabel kan man gemme værdier, som "hænger fast" ved en enkelt bruger - eller rettere ved en enkelt browser.
Session-variabler fungerer på den måde, at brugeren udstyres med en unik nøgle, når sessionen starter. I ASP bliver alle brugere automatisk udstyret med en session-nøgle. I PHP skal udvikleren starte processen med et funktionskald. Den unikke nøgle er bare en meget lang tilfældig tekststreng, der genereres første gang, hvor brugeren laver en forespørgsel til serveren. Det kan for eksempel se sådan ud: "038b77a661eb515c059a013b9a7df141". Selvom strengen er tilfældig valgt, er der ikke den store chance for, at to samtidige brugere skulle blive forvekslet, når blot nøgle-strengen er lang nok.
Når scriptet på serveren sender websiden tilbage til brugeren, sender den også en cookie med nøgle-strengen inden i. Cookien får også en "sidste-holdbarhedsdato", som angiver, hvor lang tid browseren skal gemme cookien. Ved anvendelsen af session-variabler vil det typisk være tidsrum på 30 minutter. Hver gang brugeren laver en forespørgsel, bliver holdbarhedsdatoen sat til 30 minutter, og det er altså først efter 30 minutter, hvor der ikke er aktivitet fra brugerens side, at cookien udløber, hvorefter script-maskinen antager, at brugeren har afsluttet sin session.
Når man ikke sætter udløbstiden til en længere tidsperiode, skyldes det sikkerheden. Hvis en bruger har forladt sin computer mere end en halv time, kan man forestille sig, at der kom andre forbi den pågældende computer, og som udførte yderligere og uberettigede transaktioner med den eksisterende identifikation. Det kunne føre til utilsigtede indkøb af pokemon-kort i børnefamilierne. Man skal altså have øje for brugeradfærd, når man udvikler.
Nu, hvor alle brugere er udstyret med en unik nøgle, kan script-maskinen så gemme værdier for hver enkelt bruger i en tabel i script-maskinens hukommelsesområde. Hver gang brugeren laver en forespørgsel til serveren, kan de gemte værdier så hentes frem, ved at matche brugerens nøgle med nøglen i tabellen.
Et eksempel
Logget ind eller ej?
Lad os til sidst illustrere dette med et konkret eksempel. På åbningssiden i vores webapplikation har vi en formular, hvor brugeren skal indtaste brugernavn og password. Det sker på denne måde her:
<FORM METHOD="POST" ACTION="/login.asp">
Skriv dit brugernavn:
<INPUT TYPE=TEXT NAME="brugernavn">
<P>
Skriv dit password:
<INPUT TYPE=PASSWORD NAME="password">
<INPUT TYPE="SUBMIT" VALUE="Login">
</FORM>
Her skal det lige bemærkes i parentes, at man altid skal benytte METHOD=POST. Hvis man benytter METHOD=GET kommer parametrene brugernavn og password til at stå i browserens adressefelt på denne her facon:
http://minserver/login.asp?brugernavn=Pippi&password=hrnellson
Det er ikke særlig smart, da enhver herefter kan se oplysningerne ved at kigge over skulderen på brugeren. Derfor skal man bruge METHOD=POST ved alle typer af følsomme informationer.
Lad os nu kigge på login-scriptet. I dette eksempel forudsætter vi, at brugernavne og deres tilsvarende passwords ligger i en database. Tabellen kunne hedde BRUGERE og indeholde kolonnerne ID, BRUGERNAVN og PASSWORD. Så kunne det se sådan her ud, i ASP-dialekten JScript:
// brugernavnet hentes fra formularen
brugernavn = Request.Form("brugernavn");
// password hentes fra formularen
password = Request.Form("password");
// Lav en forespørgsel til databasen,
// og se om vi finder et match:
sql = "SELECT ID FROM BRUGERE WHERE (BRUGERNAVN = '"
+ brugernavn + "') AND (PASSWORD = '"
+ password + "')";
rs = conn.Execute(sql);
if(!rs.BOF && !rs.EOF) {
// Hvis der var et match, så
// gem brugerens ID
brugerId = rs("ID");
// Gem brugerID'et i en session-variabel
Session("brugerId") = brugerId;
} else { // Hvis der ikke var et match, så:
// udskriv en passende fejlmeddelelse, og
// send brugeren tilbage til login-formularen
}
Og herefter kan brugeren ellers hoppe rundt i systemet efter forgodtbefindende.
De scripts, der forudsætter at brugeren har logget ind, skal indledes med en undersøgelse af, om der findes en variabel, der hedder brugerID. Hvis den findes, så er alt i orden: Brugeren har været igennem login-proceduren. Hvis ikke, så skal vi omstille - redirecte - brugeren til login-siden. Det gør vi sådan her:
<%
if(isEmpty(Session("brugerId"))) {
Response.Redirect("login.asp");
}
%>
Det er selvfølgelig kun et eksempel. På nettet kan man finde nøjere gennemgang af, hvordan det kodes i praksis. På det danske ASP-websted ActiveServerPages.dk kan man finde den glimrende artikel Beskyt med Brugernavn og Password, og på Webmonkey kan man finde en ligeledes glimrende indføring til sessions i PHP.
Til sidst skal vi huske på, at session-variabler kan være en rigtig slem hukommelsessluger. Hvis man putter al for meget information ind i session-variabler, skal man gange op med antallet af samtidige brugere for at vurdere hukommelsesforbruget, samt huske på, at disse variabler bliver liggende i serverens hukommelsesrum i mindst 30 minutter. Ved store datastrukturer, som for eksempel en e-kundes indkøbsvogn, er man bedre tjent ved at benytte en database. Session-variabler skal kun bruges til det, som navnet fortæller, nemlig at styre brugersessioner.