Du mechanizmai: JWT antraštėje, sesija slapuke
Dabar pastatei autentifikaciją du kartus. Pica (Android kurse) mobili programa įrodo, kas ji yra, su JWT Authorization antraštėje. SignFlow naršyklė įrodo, kas ji yra, su sesijos slapuku, paremtu duomenų baze. Ši pamoka — priežastis, kodėl vertėjo baigti visą keturių takų kursą: ta pati problema — įrodyk, iš ko ši užklausa — turi du teisingus atsakymus, ir dabar gali tiksliai pajusti kodėl.
Ką daro Pica — JWT antraštėje. Prisijungiant Pica backend pasirašo JWT ir grąžina jį atgal. Klientas jį saugo ir prideda prie kiekvienos vėlesnės užklausos per OkHttp perėmiklį:
// Pica: AuthInterceptor
val token = runBlocking { tokenStorage.getToken() }
val newRequest = originalRequest.newBuilder()
.header("Authorization", "Bearer $token")
.build()
Serveris patikrina tą žetoną vien pagal parašą — jis iš naujo apskaičiuoja parašą su savo paslaptimi ir tikrina, ar sutampa. Jokio duomenų bazės nuskaitymo: viskas, ko serveriui reikia (vartotojo ID, galiojimo pabaiga), yra žetono viduje. Būtent tai reiškia „be būsenos", ir tai — didžioji JWT stiprybė: bet kuris egzempliorius gali patikrinti bet kurį žetoną be bendros sesijų saugyklos.
Tai taip pat apibrėžiantis JWT apribojimas: jo negali atšaukti. Serveris nelaiko jokio žetono įrašo, tad neturi ką ištrinti. Pavogtas JWT galioja iki galiojimo pabaigos, taškas. „Atjunk šį vartotoją visur, dabar" — ne tai, ką grynas JWT gali.
Ką daro SignFlow — sesijos slapukas. Prisijungiant SignFlow sugeneruoja atsitiktinį neskaidrų žetoną, saugo jo SHA-256 maišą sessions lentelėje ir siunčia neapdorotą žetoną naršyklei HttpOnly slapuke. Kiekviena užklausa jį išsprendžia prieš duomenų bazę:
// SignFlow: LoadUser middleware → session.Manager.User
row, err := m.q.GetUserBySessionToken(ctx, hashToken(c.Value))
Slapukas neneša jokios informacijos — tai tik paieškos raktas. Autoritetinga sesija gyvena serveryje. Tai kainuoja duomenų bazės nuskaitymą kiekvienai užklausai, bet perka tai, ko JWT negali: atšaukimas — vienas DELETE.
// SignFlow: Logout → session.Manager.Destroy
m.q.DeleteSession(ctx, hashToken(c.Value)) // sesija dingusi; slapukas dabar negyvas
Atsijungimas, priverstinis atjungimas po slaptažodžio atstatymo, administratorius, žudantis pažeistą sesiją — visa tai viena ištrinta eilutė. Kita užklausa neranda sesijos ir yra anoniminė.
Du mechanizmai, priešingos formos: teiginiai-žetone, jokios serverio būsenos, jokio atšaukimo prieš raktas-slapuke, serverio būsena, momentinis atšaukimas. Toliau: kodėl nė vienas nėra „teisingas".