Automatyczne odświeżanie tokenów JWT w Dio: Interceptor i QueryInterceptor
Serwery weryfikują autoryzację poprzez nagłówek Authorization w formacie <schemat-autoryzacji> <parametry-autoryzacji>. Middleware przetwarza ten nagłówek dla wszystkich chronionych endpointów. W przypadku braku lub nieprawidłowości danych zwracany jest błąd 401 Unauthorized lub 403 Forbidden.
Główne schematy:
- Basic: Login i hasło zakodowane w Base64 (
Authorization: Basic bG9naW46cGFzc3dvcmQ=). Niska bezpieczeństwo ze względu na łatwość dekodowania. - Bearer: Token JWT (
Authorization: Bearer <Token>). Obsługuje czas ważności, wymaga odświeżania.
JWT składa się z Header (algorytm), Payload (dane użytkownika, exp), Signature (sekret serwera). Format: {Header}.{Payload}.{Signature} w Base64.
Struktura JWT i odświeżanie
Payload zawiera exp (czas wygaśnięcia). Przy błędzie 401 klienci żądają odświeżenia poprzez chroniony endpoint bez middleware.
Systemy dual-token:
- Access Token: Krótki czas ważności (minuty), do zapytań API.
- Refresh Token: Długi czas ważności, do generowania nowego Access Token.
Strategie obsługi wygasłych tokenów
Wylogowanie (najprostsze podejście)
Przy błędzie 401 — wylogowanie. Implementacja trywialna, ale niewygodna dla użytkowników.
Zalety:
- Minimalny kod.
Wady:
- Częste ponowne logowania.
- Niekompatybilne z dual-token.
Odświeżanie po błędzie (Interceptor)
Realizowane poprzez Interceptor w Dio. Dodaje token w onRequest, obsługuje błąd 401 w onError.
class TokenInterceptor extends Interceptor {
final TokenStorage _storage;
TokenInterceptor(this._storage);
@override
Future<void> onRequest(RequestOptions options, RequestInterceptorHandler handler) async {
final token = await _storage.getToken();
if (token != null) {
options.headers['Authorization'] = 'Bearer $token';
}
handler.next(options);
}
}
Podłączenie:
final dio = Dio()..interceptors.add(TokenInterceptor(TokenStorage()));
W onError dla błędu 401:
- Sprawdzamy flagę
_isRefreshing. - Jeśli nie — uruchamiamy odświeżanie poprzez osobny Dio.
- Kolejka zapytań czeka poprzez
Completer<void> _refreshCompleter.
Future<String?> _updateToken() async {
final oldToken = await _storage.getToken();
try {
final dio = Dio();
final response = await dio.get('/auth/refresh/', headers: {'Authorization': 'Bearer $oldToken'});
final newToken = (response.data as Map<String, dynamic>)['token'];
await _storage.setNewToken(newToken);
return newToken;
} catch (e) {
await _storage.clearToken();
return null;
}
}
Pełna logika onError synchronizuje równoległe zapytania, retry z nowym tokenem.
Zalety:
- Działa z dual-token.
- Bez zewnętrznych bibliotek.
Wady:
- Fala obciążenia serwera przy masowym retry.
- Opóźnienie odpowiedzi (błąd + odświeżanie + retry).
Proaktywne odświeżanie (QueryInterceptor)
Wykorzystuje exp z JWT do odświeżania przed wygaśnięciem. QueryInterceptor zapewnia sekwencyjne przetwarzanie zapytań.
Parsowanie JWT dla wyodrębnienia exp:
- Zdekodować Payload z Base64.
- Sparsować JSON, uzyskać timestamp.
Zaleta: brak blokujących błędów 401. Wada: sztywna kolejka, równoległe zapytania są blokowane.
Co jest ważne
- Używaj
Interceptordo reaktywnego odświeżania zCompleterdo synchronizacji. QueryInterceptornadaje się do proaktywnego odświeżania na podstawieexp.- Przechowuj tokeny w izolowanym storage, używaj osobnego Dio do odświeżania.
- Obsługuj race conditions przy wielu błędach 401.
- Dual-token zwiększa bezpieczeństwo: krótki access + refresh.
— Editorial Team
Brak komentarzy.