JSON Web Token (JWT) - Struktura i primena u oblasti autorizacije web aplikacija
Uvod
Budući da smo u jednom od prethodnih članaka detaljno predstavili postupak izrade formulara za autentifikaciju korisnika, čini se da je upoznavanje sa sistemima za autorizaciju korisnika, sledeći prirodan korak.
Pre svega, da objasnimo u čemu je razlika između dva navedena pojma:
- autentifikacija je proces utvrđivanja identiteta korisnika (praktično se proverava se da li korisnik uopšte ima pravo pristupa sistemu)
- autorizacija podrazumeva definisanje nivoa privilegija za korisnike koji su prethodno stekli pravo pristupa
Korišćenje JSON Web Tokena * predstavlja jedan od načina za autorizaciju korisnika (i glavnu temu članka), ali, prvo ćemo se ponešto detaljnije osvrnuti na problematiku autorizacije u opštem smislu.
Problematika autorizacije korisnika uopšteno
Postupak autorizacije naizgled ne deluje 'previše komplikovano', i stoga većina korisnika (i izvestan broj nedovoljno iskusnih programera), često ima utisak da posle uspešno završene prijave (to jest, posle uspešno obavljene autentifikacije), nema potrebe za preduzimanjem dodatnih koraka u situacijama kada se korisnik nadalje obraća serveru, ili (malo drugačije), deluje da se dalja komunikacija između korisnika i servera (u smislu provere identiteta korisnika i utvrđivanja privilegija), odvija "sama od sebe".
Naravno, takav utisak je u najmanju ruku varljiv, i svakako postoji razlika između toga šta se prividno dešava, i onoga što se zapravo dešava pri prosleđivanju podataka ....
Šta se naizgled dešava posle prijave korisnika?
Jednostavan odgovor na pitanje iz naslova, mogao bi biti: "Posle prijave, korisnik dobija pravo pristupa odgovarajućim sadržajima".
Koristeći se dobro poznatim primerom društvenih mreža, posle prijave na nalog (autentifikacija), korisnik dobija pristup početnoj stranici na kojoj (najčešće u gornjem delu prozora), može videti svoju sliku (avatar) i korisničko ime, dok centralni deo zauzimaju sadržaji koje su objavljivali korisnik i osobe sa kojima je korisnik u kontaktu.
Ako korisnik pozove (npr) chat aplikaciju, može nekome od prijatelja poslati poruku pod svojim imenom i (takođe), sledeći put kada otvori istu stranicu u browseru, neće morati ponovo da se prijavljuje, i dočekaće ga isti sadržaji (zapravo, najverovatnije ponešto izmenjeni sadržaji - u međuvremenu će još neko od poznanika napraviti još neku objavu).
Šta se zapravo dešava posle prijave korisnika?
Na drugo pitanje daćemo drugačiji odgovor: na površini se i dalje dešava sve ono što smo prethodno opisali, ali (pretpostavljamo da je do sada postalo sasvim jasno) - ne dešava se "samo od sebe".
Da bi sve, iz perspektive korisnika, u praksi funkcionisalo onako kako smo navikli (i naveli u prethodnom odeljku), u pozadini se odvijaju brojni procesi (o nekima smo već diskutovali, neke ćemo opisati u ovom članku, a neke u budućim člancima).
Pre svega, potrebno je osmisliti racionalan sistem za pamćenje korisničkih podataka - da korisnici ne bi morali da se prijavljuju 'svaki čas', * jer browser sam po sebi ne pamti uneto korisničko ime i lozinku automatski, što: ili znači da korisnici moraju ponovo unositi podatke za prijavu, pri sledećem pokretanju, ili treba osmisliti siguran i praktičan način za čuvanje korisničkih podataka.
Pored do sada navedenih detalja, može se desiti i to da korisnik izgubi privilegije za pristup određenoj aplikaciji (recimo, privilegije za slanje poruka u chat aplikaciji, zbog neprimerenog ponašanja i sl), pri čemu je aplikacija ostala otvorena, što, ukoliko se privilegije ne proveravaju pri slanju svakog zahteva, može dovesti do nepravilnosti u radu.
Dakle, pri slanju svakog zahteva, potrebno je obaviti autorizaciju, što podrazumeva jednu od dve prethodno pomenute mogućnosti:
- prosleđivanje podataka za prijavu (korisničko ime i lozinka)
- prosleđivanje posebno formatiranih podataka za autorizaciju (koji se čuvaju u nekom od lokalnih skladišta)
Pošto smo 'nezvanično utvrdili' da nećemo (u većini slučajeva), prosleđivati korisničko ime i lozinku uz svaki zahtev (to jest, u većini situacija je znatno praktičnije izabrati opciju #2), vraćamo se na pitanje: koje podatke je potrebno čuvati, i - u kom formatu?
Reklo bi se da je došao trenutak da u diskusiju vratimo JSON web tokene ....
JSON Web Tokeni - Struktura, oblast primene i postupak autorizacije
Kao što smo nagovestili još na početku, upotreba tokena u računarskim sistemima, u idejnom smislu se podudara sa upotrebom "žetona" u spoljnjem svetu.
U smislu pojavnog oblika, žeton je pljosnati komad plastike ili metala (najčešće okruglog oblika), koji se u ranijim vremenima koristio za javne telefone (ili, u zabavnim parkovima, za pristup električnim automobilima, ringišpilima i sl).
Međutim, u smislu značenja koje ima u određenom kontekstu, žeton (simbolično) predstavlja privilegiju osobe koja nosi žeton, da se služi određenim sadržajima.
Kao što žetoni u zabavnim parkovima omogućavaju posetiocima da koriste sadržaje bez daljeg plaćanja novcem (posle kupovine žetona), tako i web tokeni simbolično predstavljaju privilegiju korisnika da pristupi određenim sadržajima web aplikacije, odnosno, da koristi aplikaciju, tj. sajt - bez potrebe da "svaki čas" prosleđuje osetljive podatke koji su korišćeni za prijavu (korisničko ime i lozinku).
Da bi web token u praksi funkcionisao kao mehanizam za autorizaciju, potrebno je postići ravnotežu između dva oprečna zahteva: JSON web token mora sadržati dovoljno informacija za identifikaciju korisnika i dodelu privilegija, ali - tako da nijedna od zapisanih informacija ne bude poverljiva (lozinka ili drugi osetljivi podaci).
Prvo ćemo detaljno razmotriti sve zahteve koji se postavljaju pred ovakav sistem za autorizaciju, a onda ćemo se osvrnuti i na implementaciju.
Struktura JSON Web Tokena
Zahtevi koji se stavljaju pred web token su sledeći:
- token mora biti u stanju da identifikuje korisnika i omogući dodelu odgovarajućih privilegija korisniku
- navedeni zadatak potrebno je rešiti bez čuvanja osetljivih podataka o korisniku (ime, prezime, pogotovo ne korisnička lozinka i drugi osetljivi podaci)
- token obavezno mora sadržati određeni mehanizam za zaštitu podataka (kao zamenu za lozinku)
- svi podaci koji su potrebni za autorizaciju, moraju biti sadržani u samom tokenu, i treba ih čuvati na strani klijenta
Implementacija koja poštuje sve navedene zahteve, može se realizovati preko JSON objekta koji se sastoji iz tri dela:
- zaglavlje - deo u kome je naveden kriptografski algoritam preko koga nastaje potpis (poslednji deo JWT-a)
- sadržaj - glavni deo tokena, koji nosi informacije za autorizaciju (korisničko ime, nivo privilegija i druge informacije koje su neophodne za autorizaciju, ali, nisu poverljive)
- potpis - kontrolni deo koji nastaje enkripcijom kombinacije prethodna dva dela, uz korišćenje šifara (pri čemu odmah treba pomenuti da nisu u pitanju korisničke lozinke, već, šifre ili posebno formatirani 'ključevi' koji se čuvaju na serveru)
Prva dva dela JSON Web Tokena (koji odgovara navedenim zahtevima), biće enkodirani, a pre enkodiranja, navedeni delovi imaju strukturu koja je definisana po sledećem obrascu:
Potpis nastaje kriptovanjem enkodiranog zaglavlja i sadržaja, uz korišćenje šifre:
Posle završenog procesa enkodiranja, token dobija svoj finalni oblik:
Da pojasnimo dodatno: enkodiranje koje smo spomenuli (Base64), nije metoda enkripcije (odnosno, metoda zaštite), već, način da se određeni niz bajtova - pre enkripcije - svede na nisku koja odgovara određenoj specifikaciji (u konkretnom slučaju, proizvoljna niska znakova koja možda sadrži i UNICODE znake, biva svedena na nisku koja se sastoji isključivo iz ASCII slova i cifara).
Enkodiranje, kao proces koji je reverzibilan, * ne pruža nikakvu zaštitu (odnosno "skrivanje podataka").
Zaglavlje i sadržaj su direktno dostupni bilo kome ko pročita token, ali - to je sasvim u redu, budući da smo još ranije utvrdili da JWT neće nositi poverljive podatke (već samo podatke koji su i inače javno dostupni), i stoga ne moramo brinuti o tome da će preko JW tokena neko doći do poverljivih podataka.
Zaštita se zapravo postiže upotrebom hash funkcija, uz korišćenje tajne šifre ('secret'), ili ključeva (što su podaci koji se čuvaju na serveru).
Enkripcija je postupak svođenja niza bajtova na nisku proizvoljne dužine, * čiji sadržaj se ne može iskoristiti za dobijanje ulaznog podatka (u ovom slučaju, proces nije reverzibilan), ali, u pitanju je niska koja je jedinstvena (teorija nalaže da bi svakom ulaznom podatku morala odgovarati samo jedna niska koja nastaje procesom haširanja, a kada su u pitanju algoritmi koji se tipično koriste za potpisivanje i verifikaciju JW tokena, praksa još uvek nije opovrgla teoriju).
U nastavku, sledi primer vrlo jednostavne ** implementacije JW tokena uz korišćenje ugrađenog modula crypto
(implementaciju možete isprobati u radnom okruženju Node.js), a nakon toga ćemo prikazati opšte principe upotrebe JW tokena u svrhu autorizacije korisnika. ***
Implementacija u okruženju Node.js
Budući da smo se u članku o ES6 sintaksi već upoznali sa konceptom upotrebe modula u (iole) ozbiljnijim JS projektima, funkcije koje ćemo koristiti za potpisivanje i verifikaciju tokena, biće 'razdeljene' u odgovarajuće module.
Za sam početak, pripremićemo modul sa nekoliko pomoćnih funkcija.
Funkcije za enkodiranje i dekodiranje podrazumevaju dodatne korake koji se tiču pravilne obrade niski koje sadrže UNICODE znakove (odnosno, znakove kojih nema u ASCII specifikaciji), a potrebno je koristiti i pomoćnu funkciju koja 'skida' znakove '=' sa desne strane (koji se pojavljuju u slučajevima kada enkodirane niske imaju dužinu koja nije deljiva sa 4).
Tajnu šifru preporučljivo je čuvati preko zasebnog modula (sa nazivom "config"):
.... a u slučaju kada se koriste komplikovanije metode enkripcije, sa dva ključa, praktično je obavezno (i vrlo uobičajeno), da svaki ključ bude u zasebnoj datoteci.
Kreiranje potpisa
U modulu preko koga se obavlja potpisivanje tokena, ovoga puta ćemo podatke zapisati unapred, nakon čega sledi obrada:
- (budući) delovi tokena se pretvaraju u JSON objekte *
- obavlja se enkodiranje po standardu Base64
- enkodirano zaglavlje se spaja sa enkodiranim sadržajem
- poziva se funkcija za kreiranje potpisa
Na kraju, sva tri dela JW tokena, spajaju se u jednu nisku.
Verifikacija potpisa
U slučaju korišćenja metode HMAC, verifikacija potpisa može se obaviti ponovnim kreiranjem potpisa shodno podacima koji su upisani u tokenu.
Pustićemo vas da glavnu metodu osmislite sami, ali, osnovna ideja je prilično jednostavna: prvo je potrebno podeliti token na tri dela, nakon čega se obavlja dekodiranje prva dva dela; na kraju, ukoliko od 'dekodiranih delova' (uz korišćenje postupka koji smo ranije prikazali), nastane potpis koji je isti kao i potpis koji je došao uz token, sledi da je token validan.
Generator JWT-a
Ako želite da isprobate kako u praksi funkcioniše kreiranje JSON web tokena, sa podacima po vašem izboru, možete iskoristiti donji formular.
{ "alg": "HS256", "typ": "JWT" }Sadržaj:
{ "id": 42, "admin": true, "korisnicko_ime": "darth_korisnik" }Tajna šifra ("secret"):
You .... shall .... not .... pass!Token:
Primećujemo da se enkodirana verzija središnje niske menja samo "mestimično" (u onom delu koji proporcionalno odgovara mestu na kom smo napravili izmenu u ulaznoj niski), dok se potpis drastično menja pri svakoj (pa i najmanjoj) promeni sadržaja.
Upravo tako i treba da bude (i to jeste razlika između enkodiranja i haširanja).
Ostaje da prikažemo kako postupak autorizacije funkcioniše u praksi.
Postupak autorizacije korisnika preko JWT-a
Budući da se šifra za enkripciju potpisa čuva na serveru, * i budući da se korisnicima (posle uspešne autentifikacije), šalje 'potpis' koji nastaje korišćenjem šifre, za utvrđivanje autentičnosti zahteva (koji klijent kasnije šalje serveru), dovoljno je proveriti samo token koji server prima zajedno sa zahtevom.
Sadržaj tokena (koji, između ostalog, čine podaci za autorizaciju i potpis), jeste moguće izmeniti, ali, bez odgovarajuće 'tajne šifre' (ili ključeva) sa servera, praktično je nemoguće napraviti potpis koji odgovara zaglavlju i podacima za autorizaciju.
Kada browser pošalje serveru token (pri slanju zahteva za pristup korisničkoj stranici, slanju chat poruke i sl), server obavlja sledeće radnje:
- prima token
- čita zaglavlje i sadržaj
-
proverava potpis (shodno primljenom zaglavlju i sadržaju):
- ukoliko je potpis korektan - korisniku se dozvoljava pristup sadržajima
- ukoliko potpis nije korektan - korisnik gubi privilegije za pristup
Pogledajmo i primere ....
Primeri korišćenja JSON Web Tokena
Uz nekoliko slika (jer "slika vredi ~1024 reči"), lakše ćemo sagledati kako sistem zaštite funkcioniše u praksi ....
Uspešna autentifikacija
Kada korisnik prosledi korektne podatke za prijavu ....
.... na serveru se kreira token koji sadrži zaglavlje, podatke i potpis ....
.... i potom se token šalje klijentu ....
Po prijemu tokena, klijent dobija pristup aplikaciji.
Neuspešna autentifikacija
Ako korisnik ne prosledi odgovarajuće podatke za prijavu ....
.... token neće ni biti kreiran!
Za ovakve situacije se mogu implementirati rešenja koja dozvoljavaju samo određen broj pokušaja (pristupa), pre nego što se formular za pristup privremeno zablokira (na primer, 15 minuta pauze posle 5. neuspešnog pokušaja i sl), a može se koristiti i "captcha" * ili neki sličan mehanizam.
Uspešna autorizacija
U sledećoj situaciji, korisnik pred sobom već ima otvorenu aplikaciju (sa sadržajima koji odgovaraju korisničkom nalogu). Pri slanju novog zahteva, potrebno je utvrditi da li korisnik (i dalje) ima privilegije za pristup sadržajima na serveru.
Ako za primer uzmemo slanje poruke u chat aplikaciji, provera funkcioniše na sledeći način:
- klijent serveru prosleđuje zahtev za obradu podataka
- ukoliko zahtev bude prihvaćen, poslata poruka će biti dodata u bazu podataka i prikazana sa ostalim porukama
- ako zahtev bude odbijen, sistem neće uneti novu poruku u bazu, * a klijentu koji je poruku poslao, biće prosleđena poruka o grešci
- uz zahtev se prosleđuje i JSON web token.
Token se proverava po pravilima koja smo prethodno naveli (da li potpis koji je poslat uz token odgovara sadržaju) ....
.... i pošto u ovom slučaju provera daje korektan rezultat ....
.... zahtev (tj. poruka) je prihvaćen(a):
U sledećem ciklusu ažuriranja prikaza u chat aplikaciji, nova poruka (zajedno sa prethodnim porukama), biće prikazana svim klijentima koji imaju privilegije da vide poruke.
Neuspešna autorizacija
Ako neko pokuša da "prošvercuje" token sa izmenjenim sadržajem (recimo, običan korisnik pokušava da se predstavi kao administrator) ....
.... sadržaj tokena će sam po sebi "delovati" dobro, ali, pošto poslati potpis nije odgovarajući ....
.... provera neće vratiti korektan rezultat.
Token se stoga ne prihvata .... samim tim ni poruka neće biti prihvaćena (!) ....
.... a korisnik koji je pokušao da prosledi poruku uz nevažeći token, biće obavešten o tome da je došlo do greške (ostali korisnici, naravno, neće bez potrebe videti poruku upozorenja).
Kratak rezime ....
Korišćenje JSON Web Tokena uglavnom predstavlja sasvim pouzdan sistem za autorizaciju (koji je takođe i veoma popularan u poslednjih desetak godina, otkako se pojavio), ali, JSON tokeni nisu "svemogući i nepogrešivi".
U svemu što smo pisali do sada, podrazumevalo se da server "veruje" tokenu, međutim (kao što smo takođe naveli), tokeni mogu biti i kompromitovani (ne mogu se "obijati" tek tako, ali, nije ni nemoguće), i stoga ćemo temi sigurnosti tokena, temama koje su vezane za enkodiranje i enkripciju, kao i ostalim temama koje se tiču autorizacije - uskoro posvetiti više pažnje ....