Preskoči na sadržaj

HTTP autentifikacija u jeziku PHP

HTTP nudi okvir za upravljanje pravom pristupa i autentifikacijom. Mi ćemo se ovdje ograničiti na osnovnu autentifikaciju pristupa (engl. basic access authentication, Wikipedia) koja je standardizirana u RFC-u 7617 pod naslovom The 'Basic' HTTP Authentication Scheme.

Rudimentarna provjera autorizacije

Kako smo ranije dodavali druga zaglavlja, možemo u HTTP zahtjevu možemo dodati zaglavlje Authorization koje sadrži tip i vjerodajnice autorizacije za izvođenje operacije na poslužitelju (više detalja o HTTP zaglavlju Authorization na MDN-u). To zaglavlje možemo na poslužitelju pronaći u polju koje sadrži sva zaglavlja dohvaćenom funkcijom getallheaders(). Uzmimo da je za uspješnu prijavu nužno koristiti autorizaciju osnovnog tipa Basic i vjerodajnicu (u ovom slučaju kodirani zapis korisničkog imena i zaporke) YWxhZGRpbjpvcGVuc2VzYW1l. (Kasnije ćemo naučiti generirati vjerodajnice iz unesenog korisničkog imena i zaporke.) Imamo kod oblika:

<?php

$request_headers = getallheaders();
if (array_key_exists("Authorization", $request_headers) && $request_headers["Authorization"] == "Basic YWxhZGRpbjpvcGVuc2VzYW1l") {
    echo "<p>Dobrodošli.</p>\n";
} else {
    http_response_code(401);
    echo "<p>Nemate pravo pristupa.</p>\n";
}

U slučaju da provjera autorizacije ne prođe uspješno, odgovor ima statusni kod 401 Unauthorized.

Uvjerimo se da ova rudimentarna provjera autorizacije radi ispravno:

$ curl -v -H "Authorization: Ruski haker" http://localhost:8000/
*   Trying ::1:8000...
* Connected to localhost (::1) port 8000 (#0)
> GET / HTTP/1.1
> Host: localhost:8000
> User-Agent: curl/7.72.0
> Accept: */*
> Authorization: Ruski haker
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 401 Unauthorized
< Host: localhost:8000
< Date: Wed, 30 Dec 2020 00:56:39 GMT
< Connection: close
< X-Powered-By: PHP/8.0.0
< Content-Type: text/html; charset=UTF-8
<
<p>Nemate pravo pristupa.</p>
* Closing connection 0

$ curl -v -H "Authorization: Basic YWxhZGRpbjpvcGVuc2VzYW1l" http://localhost:8000/
*   Trying ::1:8000...
* Connected to localhost (::1) port 8000 (#0)
> GET / HTTP/1.1
> Host: localhost:8000
> User-Agent: curl/7.72.0
> Accept: */*
> Authorization: Basic YWxhZGRpbjpvcGVuc2VzYW1l
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Host: localhost:8000
< Date: Wed, 30 Dec 2020 00:57:11 GMT
< Connection: close
< X-Powered-By: PHP/8.0.0
< Content-Type: text/html; charset=UTF-8
<
<p>Dobrodošli.</p>
* Closing connection 0

Naravno, mi želimo da korisnik na strani klijenta unosi korisničko ime i zaporku umjesto niza znakova YWxhZGRpbjpvcGVuc2VzYW1l pa ćemo se u nastavku baviti stvarnom implementacijom osnovne varijante HTTP autentifikacije.

Implementacija autentifikacije

RFC 7235 naslovljen Hypertext Transfer Protocol (HTTP/1.1): Authentication definira općeniti autenfikacijski okvir na način:

  1. Klijent šalje zahtjev za resursom, a poslužitelj šalje odgovor sa statusnim kodom 401 Unauthorized (više detalja o HTTP statusnom kodu 401 Unauthorized na MDN-u) koji sadrži informacije o tome kako se autentificirati i zaglavlje WWW-Authenticate s najmanje jednim izazovom (više detalja o HTTP zaglavlju WWW-Authenticate na MDN-u).
  2. Klijent koji se želi autentificirati s poslužiteljem će poslati zahtjev koji sadrži zaglavlje Authorization s tipom vjerodajnicama autorizacije.

Više detalja o HTTP autentifikaciji može se naći na MDN-u.

U jeziku PHP moguće je implementirati HTTP autentifikaciju korištenjem funkcije header() za slanje HTTP zaglavlja WWW-Authenticate s odgovarajućom vrijednosti i, kao i ranije, funkcije http_response_code() za postavljanje statusnog koda 401 Unauthorized u odgovoru na zahtjev. Nakon primanja idućeg zahtjeva koji sadrži zaglavlje Authorization s odgovarajućom vrijednosti, u polju $_SERVER su dodane vrijednosti $_SERVER["AUTH_TYPE"], $_SERVER["PHP_AUTH_USER"] i $_SERVER["PHP_AUTH_PW"] koje sadrže tip autentifikacije, korisničko ime i zaporku (respektivno).

<?php

if (isset($_SERVER["PHP_AUTH_USER"]) && isset($_SERVER["PHP_AUTH_PW"])) {
    $user = $_SERVER["PHP_AUTH_USER"];
    $pw = $_SERVER["PHP_AUTH_PW"];
    echo "<p>Pozdrav $user, unijeli ste $pw kao zaporku.</p>\n";
} else {
    http_response_code(401);
    header("WWW-Authenticate: Basic realm=\"Tajni laboratorij Odjela za informatiku\"");
    echo "<p>Niste prijavljeni.</p>\n";
}

U HTTP zaglavlju WWW-Authenticate navodimo tip autentifikacije koji se koristi, u našem slučaju Basic i opis zaštićenog dijela web sjedišta (realm="Tajni laboratorij Odjela za informatiku"). Pritom uočimo da se dvostruki navodnici (znak ") koji su dio znakovnog niza (i nisu oznaka za njegov početak ili kraj) unose korištenjem znaka silazne kose crte i dvostrukog navodnika (znakovi \").

Napravimo zahtjev:

$ curl -v http://localhost:8000/
*   Trying ::1:8000...
* Connected to localhost (::1) port 8000 (#0)
> GET / HTTP/1.1
> Host: localhost:8000
> User-Agent: curl/7.72.0
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 401 Unauthorized
< Host: localhost:8000
< Date: Wed, 30 Dec 2020 17:09:28 GMT
< Connection: close
< X-Powered-By: PHP/8.0.0
< WWW-Authenticate: Basic realm="Tajni laboratorij Odjela za informatiku"
< Content-Type: text/html; charset=UTF-8
<
<p>Niste prijavljeni.</p>
* Closing connection 0

Uočimo zaglavlje WWW-Authenticate u odgovoru koje ima sadržaj opisan iznad. U cURL-u se možemo prijaviti korištenjem parametra --user, odnosno -u na način:

$ curl -v -u ivanhorvat:m0jazap0rka http://localhost:8000/
*   Trying ::1:8000...
* Connected to localhost (::1) port 8000 (#0)
* Server auth using Basic with user 'ivanhorvat'
> GET / HTTP/1.1
> Host: localhost:8000
> Authorization: Basic aXZhbmhvcnZhdDptMGphemFwMHJrYQ==
> User-Agent: curl/7.72.0
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Host: localhost:8000
< Date: Wed, 30 Dec 2020 17:15:58 GMT
< Connection: close
< X-Powered-By: PHP/8.0.0
< Content-Type: text/html; charset=UTF-8
<
<p>Pozdrav ivanhorvat, unijeli ste m0jazap0rka kao zaporku.</p>
* Closing connection 0

Vidimo da smo se uspješno prijavili. U zahtjevu postoji zaglavlje Authorization koje ima vrijednost Basic aXZhbmhvcnZhdDptMGphemFwMHJrYQ==; Basic je tip autentifikacije, a niz znakova aXZhbmhvcnZhdDptMGphemFwMHJrYQ== je kodiran shemom Base64 (više detalja o shemi kodiranja Base64 na MDN-u). Sad kad smo naučili da se radi o shemi Base64, ostavljamo čitatelju da odgonetne koje korisničko ime i zaporku kodira ranije naveden niz znakova Basic YWxhZGRpbjpvcGVuc2VzYW1l.

Dekodiranje Base64 zapisa

Kako se ne radi o šifriranju, već o jednostavnom kodiranju, lako je znakovni niz dekodirati korištenjem funkcije base64_decode() (dokumentacija).

Interpreter jezika PHP od verzije 5.1.0 nadalje omogućuje korištenje interaktivnog načina rada koji je primarno namijenjen za brzo isprobavanje kako manji dijelovi koda rade, što je vrlo korisno i u procesu učenja. Pokrenimo interaktivni način rada interpretera PHP-a korištenjem parametra --interactive, odnosno -a te izvedimo Base64 dekodiranje:

$ php -a
Interactive mode enabled

php > echo base64_decode("aXZhbmhvcnZhdDptMGphemFwMHJrYQ==");
ivanhorvat:m0jazap0rka

Zatim izvedimo kodiranje funkcijom base64_encode() (dokumentacija):

php > echo base64_encode("ivanhorvat:m0jazap0rka");
aXZhbmhvcnZhdDptMGphemFwMHJrYQ==

Warning

Slati u HTTP zahtjevu zapise kodirane shemom Base64 bez dodatnog šifriranja nije dobra sigurnosna praksa. U konkretnoj primjeni se koriste složeniji tipovi autentifikacije od ovog osnovnog i HTTPS koji uspostavlja šifrirani kanal za poruke (zahtjeve i odgovore). Mi se ovim temama bavimo s ciljem razumijevanja načina rada HTTP-a pa se ovdje ograničavamo na osnovni tip autentifikacije.

Author: Vedran Miletić