Powrót do strony głównej

Rozwój serwisu autoryzacji w Rust: tokeny, szyfrowanie, Redis

Artykuł poświęcony rozwojowi serwisu autoryzacji w Rust, obejmującemu generowanie JWT i tokenów opaque, ich weryfikację z wykorzystaniem szyfrowania asymetrycznego oraz przechowywanie w Redis. Materiał zawiera praktyczne przykłady kodu i dyskusję na temat potencjalnych podatności.

Jak zbudować serwis autoryzacji w Rust: od podstaw do zaawansowanych technik
Advertisement 728x90

Opracowywanie serwisu autoryzacji w Rust: od tokenów do orkiestracji

Tworzenie niezawodnego serwisu autoryzacji w Rust wymaga dogłębnego zrozumienia kryptografii, zarządzania tokenami i projektowania abstrakcji. Ten materiał omawia praktyczne doświadczenia z rozwoju, włączając generację JWT i opaque-tokenów, ich weryfikację i przechowywanie w Redis.

Architektura tokenów i ich generacja

Serwis wykorzystuje dwa typy tokenów: access (JWT) i refresh (opaque). Access-tokeny zapewniają krótkoterminowy dostęp, a refresh-tokeny służą do ich odświeżania bez ponownej autoryzacji użytkownika.

Do generacji JWT-tokenów stosuje się szyfrowanie asymetryczne z algorytmem RS256. To wymaga pary kluczy: prywatnego do podpisywania i publicznego do weryfikacji. Opaque-tokeny generowane są jako losowe ciągi base64, które nie niosą informacji dla klienta, co zwiększa bezpieczeństwo.

Google AdInline article slot

Kluczowe komponenty generacji

  • JwtTokenProvider: trait do generacji JWT z użyciem claims i klucza prywatnego.
  • OpaqueTokenProvider: trait do tworzenia nieprzezroczystych tokenów przez losowe bajty.
  • Struktura claims: zawiera standardowe pola, takie jak sub (identyfikator użytkownika), jti (identyfikator tokenu), iat (czas utworzenia) i exp (termin ważności).

Przykład implementacji JwtTokenProvider:

pub trait IJwtTokenProvider {
    type Claims: Send + Sync;
    type Error;
    fn generate(&self, claims: &Self::Claims, pem: &str) -> Result<String, Self::Error>;
}

Weryfikacja i walidacja tokenów

Weryfikacja JWT-tokenów odbywa się przez klucz publiczny, co zapobiega użyciu skompromitowanych kluczy prywatnych. W tym celu zaimplementowano trait IJwtTokenValidator, który sprawdza podpis i integralność tokena.

Specyfika walidacji

  • Użycie formatu PEM: klucze przechowywane są w plikach PEM, wspierających certyfikaty i łańcuchy zaufania.
  • Algorytmy szyfrowania: serwis obsługuje zarówno symetryczne (np. HSA), jak i asymetryczne (np. RSA) metody, z preferencją tych ostatnich dla większego bezpieczeństwa.
  • Konwersja claims: modele domenowe claims przekształcane są dla kompatybilności z bibliotekami, np. konwersja DateTime na usize.

Przykład kodu do weryfikacji:

Google AdInline article slot
pub trait IJwtTokenValidator {
    type Claims: Send + Sync;
    type Error;
    fn verify(&self, token: &str, pem: &str) -> Result<Self::Claims, Self::Error>;
}

Przechowywanie i orkiestracja tokenów

Do zarządzania tokenami i kluczami opracowano TokenManager i KeyManager. TokenManager odpowiada za generację, weryfikację i przechowywanie tokenów w Redis, używając abstrakcji dla elastyczności.

Implementacja RedisIO

Moduł RedisIO upraszcza pracę z magazynem, oferując metody setex, get i delete. To redukuje boilerplate-kod i poprawia czytelność.

Przykład implementacji:

Google AdInline article slot
impl<Storage> RedisIO<Storage>
where Storage: redis::AsyncCommands + Send + Sync
{
    pub async fn setex(&mut self, key: &str, data: &str, exp: u64) -> Result<(), redis::RedisError> {
        self.redis_storage.set_ex::<&str, String, (>(&key, data.to_string(), exp).await?;
        Ok(())
    }
}

Funkcjonalność TokenManager

TokenManager używa typów generycznych dla dostawców i walidatorów, co pozwala łatwo wymieniać komponenty. Główne metody:

  • generate_pair: tworzy parę access- i refresh-tokenów, zapisując je w Redis z określonym czasem życia.
  • verify_access: sprawdza ważność access-tokena i jego istnienie w magazynie.

Przykład użycia:

pub async fn generate_pair(&mut self, claims: &Claims, pem: &str) -> Result<(String, String), TokenManagerError> {
    let access_token = self.access_provider.generate(&claims, &pem)?;
    let refresh_token = self.refresh_provider.generate();
    // Logika zapisywania w Redis
    Ok((access_token, refresh_token))
}

Co jest ważne

  • Użycie szyfrowania asymetrycznego (RS256) dla JWT zwiększa bezpieczeństwo w porównaniu z metodami symetrycznymi.
  • Opaque-tokeny generowane jako losowe ciągi base64 utrudniają ich podrabianie.
  • Abstrakcje przez traits umożliwiają łatwe testowanie i modyfikowanie komponentów serwisu.
  • Przechowywanie tokenów w Redis z TTL zapewnia zarządzanie sesjami i zmniejsza obciążenie bazy danych.
  • Właściwe rozdzielenie kluczy prywatnych i publicznych jest kluczowe dla unikania luk w zabezpieczeniach.

Potencjalne problemy i ich rozwiązania

W procesie rozwoju wykryto kilka luk i trudności:

  • Wzrost rozmiaru base64: przy kodowaniu 32 bajtów na base64 ciąg powiększa się do 43 bajtów, co może prowadzić do niespodziewanych błędów w logice przechowywania.
  • Błędy w zarządzaniu kluczami: użycie klucza prywatnego zamiast publicznego do weryfikacji narusza bezpieczeństwo.
  • Trudności z typami danych: konwersja między DateTime i usize wymaga dodatkowej walidacji.

Do ich eliminacji zaleca się:

  • Testować przypadki graniczne z różnymi rozmiarami danych.
  • Wprowadzać ścisłe kontrole typów i formatów kluczy.
  • Używać bibliotek z obsługą standardów JWT i kryptografii.

— Editorial Team

Advertisement 728x90

Czytaj dalej