Wyjaśnienie tokenów odnowienia i czasu wygaśnięcia sesji w Keycloak
Dlaczego odświeżanie w Keycloak zwraca błąd 400, w jaki sposób różnią się czasy wygaśnięcia sesji Idle i Max oraz jak obsługiwać tokeny odświeżania w pojedynczych operacjach przy użyciu bezpiecznego przechowywania w JavaScript.
Źródła: Refresh Tokens and Session Timeouts in Keycloak: Why a 400 Isn’t Always a Bug
Aplikacje oparte na Keycloak czasami otrzymują od punktu końcowego tokenów odpowiedź 400 Bad Request podczas odnowienia. Szybkie wnioski wskazują na awarię procesu odnowienia. Częściej Keycloak odmawia celowo: sesja wygasła, osiągnęła maksymalny czas trwania, została anulowana lub token odnowienia nie jest już ważny. Takie sytuacje wynikają z zasad konfiguracji, a nie z błędów.
To przewodnik omawia kluczowe aspekty w środowisku produkcyjnym: jak działają tokeny dostępu i odnowienia, w jaki sposób przebiega proces odnowienia od początku do końca, które zegary sesji decydują o udanej odnowie oraz wzorce i antywzorce działania interfejsu użytkownika przy obsłudze pozostałych przypadków.
Dlaczego istnieje ten artykuł
Gdy punkt końcowy tokenów zwraca 400, zespoły często zgłaszają to jako błąd autoryzacji. Pierwsze istotne pytanie brzmi, która z reguł dotyczących czasu trwania lub anulowania tokena została uruchomiona. Zrozumienie tej odpowiedzi zapobiega traktowaniu prawidłowej odmowy jako awarii.
1. Dwa tokeny, dwa zupełnie różne zadania
Po zalogowaniu Keycloak zazwyczaj wydaje token dostępu, token odnowienia oraz token ID. Codzienne działania opierają się na pierwszych dwóch.
- Token dostępu — Dołączany do zwykłych żądań API w celu potwierdzenia tożsamości osoby wykonującej żądanie. Jego czas trwania jest krótki, często mierzony w minutach.
- Token odnowienia — Wysyłany wyłącznie do Keycloak w celu utworzenia nowego tokena dostępu. Jego czas trwania jest dłuższy, zazwyczaj trzydzieści minut lub więcej.
- Token offline — Opcjonalny token dostępu o długim czasie trwania, który może przetrwać normalną sesję SSO; regulowany jest oddzielną polityką.
Przykład z życia: token dostępu to tymczasowy bilet pokazany przy każdych drzwiach. Token odnowienia to dokument zastępujący bilet; nigdy nie jest pokazywany aplikacjom API i musi trafiać wyłącznie do punktu końcowego tokenów Keycloak.
2. Zwykły przepływ
Logowanie zwraca zestaw tokenów. Następnie klient dołącza token dostępu przy każdym wywołaniu:
GET /api/products
Authorization: Bearer eyJ...
Czas trwania dostępu jest krótki – zwykle od pięciu do piętnastu minut. Jeśli logowanie nastąpi o godzinie 09:00 przy użyciu tokena dostępu ważnego pięć minut, to już o 09:05 następne wywołanie API zwróci 401 Unauthorized.
Ten błąd 401 nie oznacza natychmiastowego wylogowania. Jest to sygnał do odświeżenia tokenów.
3. Samo wywołanie odświeżenia
Klient wysyła żądanie do endpointu tokenów Keycloak wraz z tokenem odświeżenia:
POST /realms/{realm}/protocol/openid-connect/token
Content-Type: application/x-www-form-urlencoded
grant_type=refresh_token
client_id=<client-id>
refresh_token=<refresh-token>
Poprawny odpowiedź wygląda tak:
{
"access_token": "new-access-token",
"expires_in": 300,
"refresh_token": "new-refresh-token",
"refresh_expires_in": 1800
}
Klient przechowuje nowy token dostępu, ponawia pierwotne żądanie, a użytkownik nie jest świadomy tej zmiany.
4. Szczegóły rotacji: zastępowanie przechowywanego tokena odświeżenia za każdym razem
Odpowiedź często zawiera również nowy token odświeżania. Gdy jest włączona rotacja tokenów odświeżania, poprzedni token zostaje unieważniony zaraz po wydaniu nowego. Ponowne użycie starej wartości powoduje niepowodzenie przy następnej odnowie.
// Correct
saveAccessToken(response.access_token);
if (response.refresh_token) {
saveRefreshToken(response.refresh_token);
}
// Wrong — keeps using the token that rotation just invalidated
const refreshToken = originalRefreshToken;
To, że wartość tokena odświeżania po rotacji jest przechowywana przy każdym sukcesie – a nie tylko po zalogowaniu – odróżnia sesję, która odnawia się sprawnie przez godziny, od tej, która zawodzi już przy drugiej próbie.
5. Idle versus Max: zegary kontrolujące odnowę
Realms dostarczają co najmniej dwa zegary sesji, a zamiana ich znaczeń jest przyczyną większości zgłoszeń typu „dlaczego autoryzacja nie zadziałała”:
- SSO Session Idle – Ile czasu sesja może pozostawać bez żadnej aktywności, zanim Keycloak ją zakończy. Udane odświeżenie liczy się jako aktywność i resetuje ten zegar.
Krótko mówiąc:
IDLE = how long since the session was last active
MAX = how long since the user originally logged in
Przy ustawieniach Idle = 30 minut i Max = 16 godzin częste odświeżania utrzymują krótki czas bezczynności przez cały dzień, jednak po upływie szesnastu godzin od logowania sesja kończy się, nawet jeśli użytkownik był aktywny minutę wcześniej. Idealny moment odświeżenia nie może przekroczyć maksymalnego limitu; chroni on jedynie czas trwania sesji wewnątrz tego okna.
Ustawienia Idle/Max dla poszczególnych klientów odzwierciedlają zegary serwera, ale tylko dla danego klienta. Obowiązuje ta zasada, która jest bardziej restrykcyjna.
Dlaczego w przykładach podawany jest maksymalny czas szesnastu godzin: zespół ERP pracujący w zmianie trwającej około dwunastu godzin może wybrać dwanaście godzin pracy plus czterogodzinną rezerwę, aby sesja mogła funkcjonować przez cały dzień bez konieczności ponownego logowania w trakcie zadania, ale też nie trwać bez końca. Ta liczba jest decyzją biznesową; ważną umiejętnością jest rozumienie, który mechanizm czasu jest przez nią kontrolowany.
Dlaczego nawet trzydzieści minut bezczynności nadal zaskakuje ludzi: nawet przy liberalnym limicie długi przerwa na lunch lub spotkanie może przyspieszyć wejście w stan bezczynności. Jeśli użytkownicy mają móc wrócić bez ponownego uwierzytelniania, kwestię bezczynności należy traktować jako decyzję produktową lub związaną z bezpieczeństwem – a nie jako coś, co można jedynie zamaskować w kodzie klienta.
6. Odczytywanie 400 z endpointu tokenów bez zakładania awarii
Nieudane odświeżenie może wyglądać tak:
{
"error": "invalid_grant",
"error_description": "Token is not active"
}
To dane mogą odnosić się do kilku różnych sytuacji:
- uprawnienia do odświeżenia same wygasły
Żadne z tych przypadków nie oznacza, że „API jest niedostępne”. Przeczytaj wartości error i error_description, zanim będziesz zakładać coś innego.
7. Prawidłowe radzenie sobie z tym na stronie klienta
Trwały wzorzec postępowania:
401 on API request
→ try refresh, exactly once
→ success: save new tokens, retry the original request
→ failure: clear auth state, redirect to login
Nie próbuj bez końca – najpierw błąd 401, potem odnowienie, jeśli to nie zadziała – błąd 400 i ponowne odnowienie. Jedna nieaktywna sesja może doprowadzić do lawiny żądań dotyczących tokenów oraz do elementu wizualnego oznaczającego ładowanie, który nigdy się nie zakończy. Błąd przy odnowieniu należy traktować jako ostateczny – usuń dane uwierzytelniające, wyzeruj stan autoryzacji, skieruj użytkownika na stronę logowania i zakończ proces.
8. Fan-out 401s wymagają jednej wspólnej odnowy
Panel sterowania, który wysyła pięć żądań jednocześnie z wygasłym tokenem dostępu, otrzymuje pięć błędów 401. Jeśli każda ścieżka jest odnawiana niezależnie w ramach rotacji, pierwszy udany próbny dostęp unieważnia tokeny, które nadal posiadają pozostałe cztery — w rezultacie cztery próby odnowy kończą się błędem 400 invalid_grant, mimo że sesja była poprawna.
Serializacja odnowy: pierwszy błąd 401 otwiera jedną obietnicę; pozostałe żądania czekają na tę samą obietnicę i próbują ponownie po jej zakończeniu.
if (tokenExpired) {
if (!refreshInProgress) {
refreshInProgress = refreshToken();
}
try {
const tokens = await refreshInProgress;
updateTokens(tokens);
return retryOriginalRequest();
} finally {
refreshInProgress = null;
}
}
Niezależnie od tego, czy zamek to ręcznie stworzona obietnica, kolejka interceptorów Axios czy wrapper baseQuery z RTK Query, zasada jest stała: jedna odnowa w trakcie wykonywania, bez względu na to, ilu użytkowników czeka.
9. Ustawienia, które wydają się powiązane, ale takie nie są: Czas timeoutu logowania i Czas timeoutu akcji logowania
Dwa kolejne ustawienia związane z realmami łatwo pomylić z czasem trwania sesji, jednak one dotyczą wyłącznie procedury logowania:
- Czas timeoutu logowania — jak długo cały proces autoryzacji (dane dostępowe, MFA, wymagane działania) może pozostać otwarty.
- Czas timeoutu działania logowania — jak długo pojedynczy krok (na przykład ekran z kodem OTP) może pozostać bez odpowiedzi.
Żadne z nich nie przedłuża już autoryzowanej sesji. Mają znaczenie tylko wtedy, gdy użytkownik nadal się loguje.
10. Sesje offline — inne narzędzie do innego problemu
Realmy mogą również tworzyć sesje offline za pomocą parametru scope=offline_access. Pozwalają one na uzyskanie nowych tokenów dostępu nawet po zakończeniu normalnej sesji SSO — są przeznaczone do utrzymania zalogowania „jutro i w przyszłym tygodniu”, a nie na jeden dzień pracy.
Dla zwykłego użytkowania w godzinach pracy (jedna zmiana plus bufor), dostęp offline jest zazwyczaj niepotrzebny. Optymalna wartość Session Max jest prostsza i bardziej precyzyjna. Używaj trybu offline tylko wtedy, gdy rzeczywistym wymogiem jest przechowywanie danych przez kilka dni lub tygodni.
11. Lista kontrolna do debugowania: gdy odświeżanie zwraca błąd 400
Postępuj zgodnie z tym porządkiem:
- Przeczytaj wartości
errorierror_descriptionw odpowiedzi. - Porównaj czas logowania z wartością
Session Maxna poziomie serwera i klienta. - Porównaj czas bezczynności z wartością
Session Idlena poziomie serwera i klienta. - Zbadaj dostosowania dokonane przez klienta w sekcji
Clients → <client> → Advanced.
12. Model mentalny do zachowania
Trzy warstwy, przy których zależności przepływają w jednym kierunku:
Access token → used for normal API calls
Refresh token → used to get a new access token
Keycloak session (Idle + Max) → decides whether that refresh is allowed at all
Tok odświeżania nigdy nie jest niezależny od swojej sesji. Jeśli sesja przestała istnieć – przez zbyt długi czas była nieaktywna, przekroczyła maksymalny czas ważności, została anulowana lub użytkownik się wylogował – żaden token odświeżania jej nie przywróci. To właśnie jest zamierzone działanie systemu.
List kontrolny
- Rozpatruj wygaśnięcie tokena dostępu oddzielnie od zakończenia sesji – spróbuj jednej próby odnowienia, zanim uznasz błąd 401 za wylogowanie.
- Zapisuj odnowiony token odświeżania przy każdym sukcesie, a nie tylko nowy token dostępu JWT.
- Pamiętaj o dwóch timerach: jeden odnawia sesję przy aktywności, a drugi określa maksymalny czas ważności od momentu logowania.
- Uczynij niepowodzenie odnowienia ostatecznym – jedna próba, a następnie usuń dane uwierzytelniające i poproś użytkownika o ponowne logowanie; nigdy nie próbuj ponownie w pętli.