spring-security-json

Spring Security – uwierzytelnienie przy pomocy jsona

Spring Security to narzędzie, które pomaga uporządkować kwestie związane z uwierzytelnieniem i autoryzacją. Generalnie robi to wszystko za nas. Jednak jedynym z jego minusów jest to, że nie do końca jest przystosowane do pracy z usługami restowymi z obsługą jsona. W tym artykule zajmę się właśnie tą kwestią.

Spring security domyślnie obsługuje uwierzytelnienie przy pomocy formularza (application/x-www-form-urlencoded). Jednak gdy chcemy użyć jsona w usługach restowych, trzeba to odpowiednio skonfigurować. Spring nie ma niestety żadnego magicznego przełącznika dla takich sytuacji.

 

Cały artykuł opiera się o Spring Boot. Na końcu podlinkuję końcowy rezultat, w postaci projektu na githubie.

 

Domyślna konfiguracja Spring Security

Zacznijmy od dodania dwóch prostych endpointów. Jeden z nich będzie ogólnie dostępny, drugi dostępny po zalogowaniu.

MessageDto to prosty dtos, który ma tylko jedno pole message wypełniane przez konstruktor.

Następnie dodajmy zależność do Spring Security.

W Spring Boot dodanie zależności w gradlew wystarczy, żeby uruchomiła się autokonfiguracja. Od tego momentu, po wejściu na jakikolwiek endpoint, Spring będzie nas przekierowywał na domyślny formularz logowania (/login), który dostarcza biblioteka.

sing in form

Do tego formularza można zalogować się używając użytkownika user i hasła wygenerowanego przez waszą aplikację. Będzie ono widoczne w logach w takiej formie:

Po zalogowaniu, aplikacja powinna przekierować Cię do root contextu (/) i powinieneś zobaczyć komunikat:

 

Logowanie przy użyciu wygenerowanego hasła jest mało wygodne, ponieważ zmienia się ono po każdym restarcie aplikacji. Dodajmy więc użytkownika i hasło, tak by testowanie było wygodniejsze.

Dodaj klasę konfiguracyjną SecurityConfig, która dziedziczy po WebSecurityConfigurerAdapter i zawiera metodę, konfigurującą nam w pamięci bazę użytkowników (możemy w ten sposób konfigurować dowolną ilość użytkowników):

Konfigurujemy użytkowników pamięci tylko na potrzeby testów. W produkcyjnej aplikacji użytkownicy najczęściej będą przechowywani w bazie danych.

I tutaj możesz natknąć się na taki błąd (to istotne, bo różne źródła podają różny sposób konfiguracji Spring Security):

Błąd ten oznacza, że musisz jawnie podać, w jaki sposób jest zakodowane twoje hasło (w Spring Security 5 zmienił się format przechowywania hasła i teraz przed hasłem trzeba podać algorytm kodowania hasła). Na potrzeby testów korzystamy z inMemoryAuthentication, a dla wygody nie będziemy kodować hasła, więc potrzebujemy przed hasłem dodać {noop}:

Możesz także skorzystać z różnego rodzaju algorytmów hashujących takich jak: {bcrypt}, {pbkdf2}, {scrypt}, {sha256}

Dostosowanie konfiguracji do własnych potrzeb

Jak już napisałem wcześniej, po wejściu na dowolny endpoint zostaniesz przekierowany na formularz logowania. Przekierowanie to nie jest nam potrzebne, bo chcemy logować się do aplikacji za pomocą jsona wysyłanego postem, a zamiast przekierowania chcemy zawsze otrzymywać błąd 401 Unauthorized. Dodajmy więc odpowiednią konfigurację, która pozwoli wyłączyć przekierowanie, a także pozwoli wejść na root context (/) bez logowania.

Warto także włączyć debugowanie w security(@EnableWebSecurity(debug = true) – tylko na czas developmentu). Dodajemy kolejną metodę configure(HttpSecurity http) wraz z konfiguracją:

  1. Wyłączamy obsługę csrf tokena – nie będzie nam tutaj potrzebna.
  2. Pozwalamy wchodzić na root context (/) aplikacji bez logowania.
  3. Pozwalamy wchodzić bez logowania na formularz – co jest odwzorowaniem domyślnej konfiguracji.

Po tych zmianach, jedyna różnica jest taka, że root context (/) aplikacji jest dostępny bez logowania.

Teraz musimy dodać dodatkową konfigurację, która sprawi, że zamiast przekierowania na zabezpieczonych endpointach dostaniesz błąd 401 Unauthorized.

  1. Te dwie linijki sprawiają, że po wejściu na zabezpieczony endpoint, otrzymasz błąd 401 Unauthorized, zamiast przekierowania na formularz logowania.

 

W tym miejscu warto zaopatrzyć się w jakiegoś desktopowego klienta http, ponieważ domyślny formularz przestanie już działać i nie będziesz mógł na niego wejść. Ja użyłem Postmana.

Nadal możemy  zalogować się za pomocą endpointu /login.

postman

Po zalogowaniu się możemy już wejść na endpoint secured (Postman zajmie się obsługą cookie za ciebie):

postman-secured

 

Wszystko działa, ale nadal endpoint /login konsumuje dane w formacie application/x-www-form-urlencoded i nie jest to spójne z usługami restowymi, które konsumują i produkują jsona (application/json). Kolejna kwestią jest to, że ciągle po zalogowaniu mamy przekierowanie na root context (/) aplikacji i usługa logowania zwraca {"message": "Hello world"}. Wszystkich te problemy rozwiążemy w następnym kroku.

Żeby zmienić opisane powyżej zachowania, musisz nadpisać domyśli filtr UsernamePasswordAuthenticationFilter, który jest odpowiedzialny za odczytywanie danych z formularza, tak żeby odczytywał dane z jsona.

Klasa przechowująca dane logowania:

Dodatkowo, musisz także odpowiednio skonfigurować instancję filtra tak, żeby korzystała z domyślnego authentication managera oraz musisz ustawić własną implementację successHandlera i failureHandlera, co pozwoli na pozbycie się przekierowania po poprawnym zalogowaniu:

  1. Dodajemy successHandler, który zaimplemntujemy tak, by nie przekierowywał.
  2. Następnie dodajemy standardowy failureHandler.
  3. Ustawiamy domyślny authenticationManager.

Poniżej kod successHandler:

i failureHandler:

 

Ostatnią rzeczą jest dodanie filtra do głównej konfiguracji Spring Security:

  1. Konfigurujemy filtr
  2. Usuwamy .formLogin().permitAll()

W tym miejscu wszystko powinno już działać. Nie zapomnij w Postmanie zmienić Content-Type na application/json. I koniecznie wyłącz debugowanie Spring Security @EnableWebSecurity(debug = false).

Podsumowanie

Muszę przyznać, że już kilka razy implementowałem logowanie w usługach restowych ze Spring Security. I za każdym razem wygląda to trochę inaczej. Ale ten sposób, który tu przedstawiłem jest chyba najbardziej zwarty, więc może przyda mi się kolejnym razem 😉 Mam nadzieję, że tobie też.

Link do projektu na githubie: security-example

Żródła:

https://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/

https://docs.spring.io/spring-security/site/docs/current/api/org/springframework/security/web/authentication/UsernamePasswordAuthenticationFilter.html

 

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

6 thoughts to “Spring Security – uwierzytelnienie przy pomocy jsona”

    1. Tak, jest. Cały ten przykład jest oparty o tradycyjną sesję http (klient przesyła cookie).

      1. A, spoko. Uczę się dopiero Springa i zmyliło mnie połączenie wzmianki o „usługach restowych” i formlogin w jednym wpisie i myślałem, że coś źle rozumiem. Dzięki za szybką odpowiedź!

        1. Jasne. W artykule opisuje tylko jak to zrobić jsonem. Nie poruszam innych problemów. Chciałem to opisać najprościej jak się da, więc dlatego jest na sesji http.

          I dopiero teraz przyszło mi do głowy, że linijka .formLogin().permitAll() w przykładzie na samym końcu artykułu jest zbędna 😉 Poprawię to. Dzięki za komentarz.

Dodaj komentarz

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