Co naprawdę zawiera token JSON Web Token
Token JSON Web Token (JWT) to trzy fragmenty tekstu połączone kropkami. Każdy fragment jest zakodowany w Base64url, a dwa pierwsze to zwykły JSON. Każdy, kto ma token, może odczytać, co jest w środku. Nic w zwykłym tokenie JWT nie jest zaszyfrowane.
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9 <- header
eyJzdWIiOiIxMjM0NSIsIm5hbWUiOiJBbGV4In0 <- payload
dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFW <- signature
Połączone kropkami dają token, który wkleja się do żądania. Zdekoduj dwie pierwsze części, a otrzymasz zwykły JSON:
{ "alg": "HS256", "typ": "JWT" }
{ "sub": "12345", "name": "Alex" }
Prawdziwy ładunek niesie zwykle również znaczniki czasu i odbiorcę:
{
"iss": "https://auth.example.com",
"sub": "12345",
"aud": "api.example.com",
"iat": 1756000000,
"exp": 1756003600,
"roles": ["editor"]
}
Nagłówek
Nagłówek opisuje sposób podpisania tokenu. Największe znaczenie mają dwa pola:
algnazywa algorytm podpisu, na przykładHS256(HMAC z SHA-256, z użyciem współdzielonego sekretu) alboRS256(podpis RSA z SHA-256, składany kluczem prywatnym i sprawdzany publicznym).kid, czyli identyfikator klucza, jest opcjonalny i mówi odbiorcy, którego z kilku kluczy użyto, dzięki czemu klucze można rotować bez unieważniania istniejących tokenów.
typ to zwykle po prostu JWT i nie niesie żadnego znaczenia dla bezpieczeństwa.
Ładunek i jego oświadczenia
Ładunek to obiekt JSON złożony z oświadczeń, co jest po prostu słowem, którym specyfikacja nazywa stwierdzenia o podmiocie tokenu. Część nazw oświadczeń jest zarejestrowana w standardzie i wszystkie są krótkie:
| Oświadczenie | Nazwa | Co zawiera |
|---|---|---|
iss | Wystawca | Kto utworzył token, zwykle adres URL albo nazwa usługi |
sub | Podmiot | Kogo token dotyczy, zwykle stały identyfikator użytkownika |
aud | Odbiorca | Dla kogo token jest przeznaczony, żeby usługa mogła odrzucić tokeny przeznaczone dla innej |
exp | Czas wygaśnięcia | Chwila, po której token musi zostać odrzucony |
nbf | Nie wcześniej niż | Chwila, przed którą token musi zostać odrzucony |
iat | Czas wystawienia | Kiedy token został utworzony |
jti | Identyfikator JWT | Unikalny identyfikator, przydatny do śledzenia albo blokowania pojedynczych tokenów |
exp, nbf i iat są wartościami typu NumericDate, czyli liczbą sekund od 1 stycznia 1970 roku UTC. Są to sekundy, a nie milisekundy. Jeśli znacznik czasu dekoduje się na datę w roku 57 000, czytasz milisekundy i musisz podzielić przez tysiąc.
Wszystko inne w ładunku jest tym, co umieścił tam wystawca: role, uprawnienia, adres e-mail, identyfikator najemcy. Ponieważ prywatna nazwa oświadczenia mogłaby kiedyś zderzyć się z nazwą zarejestrowaną, wystawcy często opatrują własne nazwy przedrostkiem przypominającym adres URL.
Podpis
Podpis jest liczony z dokładnego tekstu dwóch pierwszych części połączonych kropką, z użyciem algorytmu z nagłówka i klucza. Zmień jeden znak nagłówka albo ładunku, a podpis przestaje pasować.
Przy HS256 ten sam sekret zarówno podpisuje, jak i weryfikuje, więc każdy, kto potrafi sprawdzić token, potrafi też go wytworzyć. Przy RS256 albo ES256 podpisuje klucz prywatny, a weryfikuje publiczny, i to właśnie pozwala dostawcy tożsamości wydawać tokeny, które wiele osobnych usług może zweryfikować, nie przechowując niczego tajnego.
Base64url to nie szyfrowanie
Base64url to ten sam pomysł co Base64, z dwiema różnicami: używa - i _ w miejsce + i /, dzięki czemu wynik jest bezpieczny w adresach URL, a końcowe dopełnienie = jest zwykle usuwane. Jest to kodowanie, czyli odwracalny sposób zapisu bajtów jako tekstu. Nie zapewnia absolutnie żadnej poufności.
Nigdy więc nie umieszczaj w ładunku tokenu JWT niczego wrażliwego: żadnych haseł, numerów kart, wewnętrznych notatek o użytkowniku. Zakładaj, że osoba trzymająca token oraz każdy, kto odczyta go z pliku logu albo z pamięci przeglądarki, widzi każde pole.
Istnieje osobny format, JWE, który faktycznie szyfruje ładunek i który ma pięć części zamiast trzech. Jeśli twój token ma trzy części, jest podpisany i czytelny.
Zdekodowanie to nie weryfikacja
To jest punkt, który ma znaczenie. Dekoder pokazuje, co token mówi. Nie mówi, czy token jest autentyczny. Weryfikacja jest osobnym krokiem i wymaga klucza.
Żeby naprawdę zaufać tokenowi, serwer musi:
- Sprawdzić podpis wobec właściwego klucza.
- Wymagać algorytmu, którego oczekuje, zamiast ufać polu
algw tokenie. Z pominięcia tego kroku biorą się dwa klasyczne ataki: ustawieniealgnanonei usunięcie podpisu oraz wzięcie publicznego klucza RSA usługi i użycie go jako sekretu HMAC, przez co weryfikatorRS256zostaje podstępem zmuszony do wykonaniaHS256. - Sprawdzić
exporaz, jeśli występuje,nbf, dopuszczając niewielki rozjazd zegarów. - Sprawdzić, czy
issiaudzgadzają się z tym, czego oczekuje ta usługa.
Dopiero wtedy oświadczenia cokolwiek znaczą. Do tego momentu ładunek jest niezweryfikowanym ciągiem znaków, który przyszedł z sieci.
Uwagi praktyczne
- Tokeny wysyła się zwykle w nagłówku HTTP jako
Authorization: Bearer <token>, więc trzymaj ładunek małym. Każde dodane oświadczenie jest wysyłane przy każdym żądaniu. - Podpisany token pozostaje ważny aż do wygaśnięcia. Nie ma wbudowanego sposobu na jego unieważnienie i dlatego tokeny dostępu mają zwykle krótki czas życia, często liczony w minutach, a do uzyskiwania nowych służy osobny token odświeżający.
- Jeśli token wygląda na uszkodzony, najpierw policz kropki. Dwie kropki i trzy części to normalny podpisany token JWT. Bardzo częstą przyczyną komunikatu "invalid signature" jest token ucięty przy wklejaniu z terminala.
- Zdekodowanie tokenu w celu odczytania terminu ważności, podmiotu albo ról podczas diagnozowania jest całkowicie bezpieczne i nie wymaga żadnego sekretu. Dokładnie po to ładunek istnieje.