spring security jwt

Spring Security i Json Web Token

Po poprzednim artykule Spring Security – uwierzytelnienie przy pomocy jsona, kilka osób odezwało się do mnie z pytaniem, czy mógłbym opisać uwierzytelnienie z wykorzystaniem JWT (Json Web Token)? Nie jest to trudne zadanie i nie wymaga zbyt wiele pracy w stosunku do tego, co napisałem w poprzednim artykule, więc postanowiłem to krótko opisać.

Bazując na przykładzie z poprzedniego artykułu, wprowadzę kilka zmian, które pozwolą logować się do aplikacji za pomocą JWT. Wcześniejszy przykład jest oparty o bardziej tradycyjne podejście z sesją http po stronie serwera i wymianą cookie po stronie klienta.

 

Poprzedni przykład Spring Security – uwierzytelnienie przy pomocy jsona miał pokazywać tylko, jak korzystać z jsona przy logowaniu, zamiast tradycyjnego formularza application/x-www-form-urlencoded. I nie poruszał on żadnych innych zagadnień (nie dla wszystkich było to jasne).

 

Zacznę od tego, czym w ogóle jest JWT? Jest to ciąg znaków, który powstaje w wyniku zakodowania obiektu w formacie json, zawiera jakieś informacje np. o użytkowniku (nazwę użytkownika). Jedną z jego charakterystycznych cech jest to, że ma z góry określony czas życia (czas jego ważności). Kolejną zaś to, że jest podpisany sygnaturą, co sprawia, że bez jej poprawnej weryfikacji, token zostanie uznany za niepoprawny. Token zakodowany jest algorytmem base64 i można go łatwo odkodować, więc nie powinien zawierać informacji wrażliwych (jest to uproszczona definicja tokena JWT- jeśli potrzebujesz bardziej szczegółowych informacji o JWT, znajdziesz je tutaj).

 

Json Web Token sekwencja

 

Jak działa autoryzacja za pomocą tokena?

Autoryzacja ta opiera się na wymianie tokena JWT pomiędzy klientem i serwerem. Nie różni się to aż tak bardzo, jak wymiana cookie. Różnica jest po stronie serwera. Nie ma sesji http, więc aplikacja pod tym względem jest bezstanowa. Tokeny generowane są w aplikacji poprzez bibliotekę com.auth0:java-jwt i wyglądają tak jak na przykładzie poniżej:

 

Pierwszą rzeczą, jaką musimy zrobić, jest dodanie zależności do biblioteki, która stworzy dla nas token i podpisze go przy pomocy naszej sekretnej frazy. Dopisuję więc w zależnościach:

 

Kolejna rzecz to sprawienie, żeby po zalogowaniu endpoint /login zwracał nam token. Najłatwiej jest to zrobić w succesHadler. Musisz przeimplementować metodę onAuthenticationSuccess(...) tak, żeby zwracała tokena. W succesHadler mamy dostępny OutputStream, który posłuży nam do zwrócenia tokena. Możemy to zrobić na trzy sposoby:

  • zwrócić tokena w nagłówku Authorization
  • jako zwykłego stringa
  • zwrócić jsona

Ja wybrałem trzecią opcję:

  1. Pobieram szczegóły zalogowanego użytkownika.
  2. Tworzę builder.
  3. Dodaję nazwę użytkownika jako subject.
  4. Ustawiam datę wygaśnięcia. Zmienna expirationTime jest ustawiana w konfiguracji aplikacji.
  5. Ustawiam algorytm na HMAC256 dla secret (ta zmienna też jest pobierana z konfiguracji). Podpisuję i tworzę tokena (metoda .sing() robi te dwie rzeczy)
  6. Wypisuje jsona zawierającego tokena na OutputStream. Robię to w bardzo prosty sposób (konkatencja), ponieważ tworzenie obiektu opakowującego i zamienianie go na jsona poprzez ObjectMapper wydało mi się trochę nadmiarowe.

 

Jeśli chcesz zwracać tokena w nagłówku http, możesz to zrobić w takiej formie:

 

Endpopint /login powinien zwracać w tym miejscu jsona z tokenem w postaci:

Takiego tokena możesz sprawdzić używając serwisu https://jwt.io, wystarczy wkleić go na stronie :

 

Autoryzacja zabezpieczonych endpointów

Gdy już mamy zwróconego tokena, możemy zacząć autoryzować się do zabezpieczonych endpointów. Token będziemy wysyłać w dodatkowym nagłówku http Authorization w takiej formie:

Żeby prawidłowo obsłużyć ten nagłówek, będziemy potrzebowali stworzyć odpowiedni filtr, który będzie dziedziczył po klasie BasicAuthenticationFilter. Tworzymy nową klasę JwtAuthorizationFilter i nadpisujemy metodę doFilterInternal(...)

  1. Pobieram obiekt autoryzacji, jeśli nie istnieje, przekazuję sterowanie do kolejnego filtra.
  2. Jeśli istnieje, ustawiam obiekt w SecurityContextHolder.
  3. Pobieram tokena z nagłówka Authorization sprawdzam, czy zawiera prefix Bearer.
  4. Inicjalizuję weryfikację tokena.
  5. Używam metody verify(...) do sprawdzenia poprawności sygnatury tokena (przy okazji wycina z tokena prefix).
  6. Pobieram subject (w tym wypadku jest to nazwa usera).
  7. Pobieram użytkownika z userDetailsManagera (którego wcześniej wstrzykuję w konstruktorze).
  8. Tworzę UsernamePasswordAuthenticationToken w oparciu o pobrane UserDetails.

 

W pliku application.properties, dodaję konfigurację:

Możesz dostosować konfigurację w zależności od potrzeb. Zwykle tokeny są ważne kilka godzin lub nawet kilka dni. Nie zapomnij też, by ustawić unikalny secret.

 

W tym miejscu mamy już dostępne wszystkie składniki potrzebne do korzystanie z JWT w naszej aplikacji. Ostatnią rzeczą, jaką musimy zrobić jest zmiana konfiguracji.

  1. Konfiguruję sesję uwierzytelnienia tak, by była bezstanowa.
  2. Dodaję do konfiguracji nowo utworzony filtr JwtAuthorizationFilter, jako parametry podaję authenticationManager(), userDetailsManager, który wstrzykuję do konfiguracji i secret, który wstrzykuję poprzez @Value("${jwt.secret}").

 

Podsumowanie

Json Web Token to prosty sposób, by uczynić aplikacje bezstanowymi. Dzięki niemu zwiększysz swoje możliwości, jeśli chodzi o skalowanie aplikacji. W sytuacji, gdy zajdzie konieczność uruchomienia kilku węzłów aplikacji, nie będziesz potrzebował takich rozwiązań jak sticki session, czy replikacji sesji pomiędzy węzłami (upraszcza to architekturę).

 

Podziel się opinią!

Jeśli podobał Ci się artykuł, daj mi znać o tym w komentarzach. Jeśli masz jakieś pytania, to też zapraszam cię do komentowania lub skorzystanie z formularza kontaktowego Kontakt.

Zachęcam Cię także do zapisania się do newslettera i polubienia mojej strony na Facebooku

 

Link do projektu na githubie: security-example-jwt

 

Żródła:

https://jwt.io/

https://github.com/auth0/java-jwt

 

Zapisz się na Newsletter

Mateusz Dąbrowski

Cześć jestem Mateusz, zajmuję się programowaniem już ponad 12 lat i zachęcam Cię do lektury mojego bloga

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *