Archiwa tagu: MongoDB

Spring Boot: autoryzacja użytkowników w oparciu o bazę danych

O logowaniu użytkowników pisałam wcześniej w następujących postach:

Dzisiaj dodałam do mojej raczkującej aplikacji funkcjonalność logowania użytkowników zapisanych wcześniej w bazie danych.

Poniżej przedstawiam zmiany, które musiałam w tym celu wprowadzić do mojej aplikacji.

a)  Wersja podstawowa

W klasie User, która od teraz ma być nie tylko klasą przechowującą dane, ale także bazą do autoryzacji użytkowników, muszę zaimplementować interfejs UserDetails:

W klasie SecurityConfiguration opisuję nowy sposób logowania:

Brakuje mi odpowiedniej metody do pobrania obiektu użytkownika po jego nazwie (getUserByUsername) w klasie UserRepository. Do tej pory szukałam użytkowników jedynie na podstawie confirmationId (co opisałam tutaj: Aktywacja konta poprzez email). Na szczęście dzięki Spring Data nie muszę jej nawet implementować, wystarczy, że dodam następującą linię:

b) Wersja rozszerzona. Przecież ja szyfruję hasła!

W tym celu muszę zarejestrować odpowiedni bean szyfrujący:

Jego właśnie muszę użyć do zaszyfrowania hasła przy tworzeniu nowego użytkownika:

Należy też wspomnieć o nim w konfiguracji:

Klasa BCryptPasswordEncoder implementuję metodę matches z interfejsu PasswordEncoder, która sprawdza, czy hasło w czystej postaci pasuje do jego zahaszowanej wersji przechowywanej w bazie danych.

Już.

PS. Podczas testowania mój dostawca domeny i serwera zablokował mi konto email, przekonany, że ktoś się na nie włamał i rozsyła spam

Aktywacja konta poprzez email

Poprzedni wpis poświęciłam kwestii wysyłania maili przez aplikację napisaną w Javie.

Kontynuując, teraz chcę upewnić się, że świeżo zarejestrowany użytkownik to człowiek (a nie bot) i że podał poprawny adres email. Poniżej opisuję kroki prowadzącego do tego celu.

Krok 1: nieaktywny użytkownik z wygenerowanym kodem

Po wypełnieniu formularza rejestracyjnego obiekt reprezentujący użytkownika jest zapisywany w bazie danych jako nieaktywny. Razem z nim zapisywany jest losowo wygenerowany kod do aktywacji konta. Na tym etapie użytkownik nie może się logować, nie zna też przypisanego mu kodu.

Krok 2: email z linkiem aktywacyjnym

Aplikacja wysyła na adres podany przez użytkownika wiadomość zawierającą link aktywacyjny. Kod wygenerowany losowo w poprzednim kroku jest doklejony do adresu jako parametr URL.

Krok 3: obsługa linku aktywacyjnego

Wejście pod adres podany w mailu powoduje uruchomienie servletu weryfikacyjnego. Aplikacja szuka w bazie danych obiektu użytkownika, któremu przypisano kod podany jako parametr. Jeśli taki użytkownik istnieje, jest oznaczany jako aktywny. Od tej chwili może się już logować, ponieważ potwierdzono (czy raczej zwiększono prawdopodobieństwo), że to rzeczywista osoba.

Krok 4: usunięcie nieaktywnych kont

Należałoby usunąć zbyt długo przechowywane nieaktywne konta. Ale jeszcze się za to nie zabrałam 🙂

Ciekawostka: wyszukanie użytkownika w bazie

Oto ilość pracy, którą włożyłam w wyszukanie użytkownika w bazie danych MongoDB po wartości confirmationId:

Resztę robi za mnie Spring Data, parsując nazwę metody!

Więcej informacji na ten temat można znaleźć tutaj.

PS.

To ostatni techniczny wpis w ramach konkursu Daj się poznać 2016. Jutro postaram się napisać krótkie podsumowanie. O ile wieczorem na spotkaniu młodych mam nie poleje się za dużo wina.

 

Baza danych w chmurze Pivotal (photo story)

Pokazywałam już, jak wdrożyć aplikację webową w chmurze PaaS Pivotal Web Services:

  1. Wdrażam moją aplikację (która nadal nic nie robi) w chmurze Pivotal Web Services

Pisałam też co nieco o nierelacyjnej bazie danych MongoDB:

  1. Jak zacząć z MongoDB?
  2. MongoDB + Spring Data
  3. Zapisuję dane użytkowników w bazie i robię głupie błędy podczas walidacji formularza

Jak zapewnić sobie dostęp do serwera bazodanowego działającego w chmurze?

Do tej pory uruchamiałam aplikację tylko lokalnie. Przed odpaleniem pliku .jar z wbudowanym serwerem Tomcat musiałam najpierw uruchomić lokalny serwer bazy danych.

W serwisie Pivotal istnieje tzw. Marketplace, w którym można kupować dodatkowe usługi. Wygląda to tak:

Lista dodatkowych serwisów Pivotal
Lista dodatkowych serwisów Pivotal

Odnalazłam na liście interesujący mnie element:

MongoDB w chmurze Pivotal
MongoDB w chmurze Pivotal

Moim celem jest teraz włączenie serwisu i powiązanie go z moją aplikacją Szafbook.

Mogę zrobić to z wiersza poleceń przy użyciu narzędzia cf (muszę wówczas użyć poleceń cf create-service, cf bind-service i ewentualnie cf-restage), mogę też wyklikać swoje wymagania w przeglądarce, na stronie Pivotal:

Banalnie prosta konfiguracja usługi
Banalnie prosta konfiguracja usługi

W przypadku wielu dodatkowych serwisów muszę wybrać pomiędzy kilkoma „planami” – najczęściej jeden z nich jest darmowy i dość ograniczony (np. 5 jednoczesnych połączeń, 20MB pojemności), a kolejne są coraz mocniejsze i coraz droższe. MLab na razie ma tylko darmową wersję.

Po kilkunastu sekundach mam już działające połączenie pomiędzy bazą a aplikacją:

Powiązanie serwisu MLab (MongdoDB as a service) z aplikacją w chmurze Pivotal Web Services
Powiązanie serwisu MLab (MongdoDB as a service) z aplikacją w chmurze Pivotal Web Services

Wszystko działa!

Działająca aplikacja :)
Działająca aplikacja 🙂

Chmura jest w stanie samodzielnie podmienić źródło danych aplikacji w taki sposób, by odwoływało się do nowoutworzonego serwisu. Programista nie musi nawet znać adresu tej usługi! Choć, oczywiście, może go sobie podejrzeć:

Więcej informacji na temat instancji MongoDB podpiętej do mojej aplikacji (na stronie dostawcy usługi)
Więcej informacji na temat instancji MongoDB podpiętej do mojej aplikacji (na stronie dostawcy usługi)

W rzeczywistości, jak zwykle, nie obyło się bez błędów:

  1. Opierając się o tutorial, wyklikałam dostęp do serwisu ElephantSQL, czyli bazy relacyjnej bazy PostgreSQL zamiast do MLab czyli MongoDB.
  2. Rozdzieliłam właściwości projektu pomiędzy kilka plików dla różnych profili (np. application-development.properties), po czym zapomniałam zmienić profil (z development na cloud) przy budowaniu na potrzeby wdrożenia.

 

 

Zapisuję dane użytkowników w bazie i robię głupie błędy podczas walidacji formularza

Zapis danych użytkowników w MongoDB

O MongoDB pisałam już w dwóch wcześniejszych postach: Jak zacząć z MongoDB? i MongoDB + Spring Data.

Dzisiaj postanowiłam zaimplementować wreszcie zapis danych z formularza użytkownika w mojej nierelacyjnej (dokumentowej) bazie danych.

W poprzednim tekście pokazałam, jakich adnotacji należy użyć w klasie User, żeby jej instancje zostały poprawnie zapisane w bazie danych. Żeby faktycznie zapisać dane z formularza, musiałam tylko dopisać kilka linii w kodzie kontrolera:

Co zrobić z hasłami?

Zależało mi na spełnieniu trzech warunków:

  1. Użytkownik podaje hasło dwa razy, żeby zmniejszyć ryzyko literówki.
  2. Wpisywane hasło jest niewidoczne.
  3. Hasło w bazie danych nie jest przechowywane w postaci jawnej.

Dwa hasła

Dodałam następujące pola do klasy reprezentującej użytkownika:

Specjalnie zaznaczyłam adnotację @Transient. Dzięki jej użyciu wskazane pola (w tym wypadku hasła zapisane jawnym tekstem) nie zostaną zapisane w bazie danych.

Ukrycie literek

Pola musiałam również dopisać do formularza. Użyłam pola input w wersji password, żeby zamiast liter wyświetlić gwiazdki (czy tam kropki).

Funkcja hashująca

Wreszcie, hasła. Nie powinny być przechowywane w bazie danych w postaci jawnej. Powodów jest wiele. Na przykład taki: ludzie często używają tych samych haseł w wielu miejscach. Gdyby administrator bazy danych miał wgląd w hasła do aplikacji, mógłby podjąć próbę zalogowania się na konta użytkowników w innych serwisach (np. gmail) przy użyciu tego samego hasła i adresu email.

Oprócz pokazanych już dwóch pól do wprowadzenia hasła roboczego, klasa User przechowuje jeszcze następujące dwie wartości:

passwordSalt to losowa wartość doklejana do hasła przed jego „zaszyfrowaniem” (dzięki niej w bazie danych nie będzie widać, że dwa użytkownicy przypadkowo użyli tego samego hasła), passwordEncrypted to wynik szyfrowania.

Losowanie i szyfrowanie realizuje następujący pomocniczy serwis, wywoływany z kontrolera dzięki adnotacji @Autowired:

Kontroler wygląda tak:

Błąd 1: strona błędu zamiast komunikatu o błędzie w formularzu

Podanie niepoprawnych danych w formularzu z nieznanego mi powodu zaczęło przenosić mnie na moją własną stronę błędu (zamiast wyświetlenia komunikatu przy polu formularza, które spowodowało problem).

Co się okazało?

W pewnym momencie zmieniłam sygnaturę metody kontrolera z:

na:

ponieważ chciałam dopisać informacje do modelu.

Okazuje się, że parametr reprezentujący BindingResult musi następować bezpośrednio po parametrze, przez który przekazywany jest dotyczący go obiekt (co ma sens, gdy jeden formularz obsługuje wiele obiektów).

Formularz zaczął działać poprawnie po zamianie kolejności parametrów:

Błąd 2: Error during execution of processor ‚org.thymeleaf.spring4.processor.attr.SpringErrorsAttrProcessor’

Kolejny błąd zaskoczył mnie, kiedy testowałam sytuację, w której użytkownik wpisze dwa różne hasła. Okazało się, że poczyniłam złe założenia na temat parametrów metody rejectValue, za pomocą której chciałam przekazać do formularza informację o wykryciu problemu. Napisałam:

zakładając, że drugi parametr to komunikat o błędzie. Tymczasem – to kod błędu. Komunikat można przekazać dopiero w trzecim parametrze (poprawny kod widać na listingu z metodą processPasswords). Błąd wynikał zatem z braku komunikatu o błędzie, który był wymagany w szablonie.

Wreszcie działa

Kod jest dostępny w moim repozytorium GitHub.

Komunikat o niepasujących do siebie hasłach
Komunikat o niepasujących do siebie hasłach
Wyświetlenie informacji o pierwszym lepszym użytkowniku z bazy
Wyświetlenie informacji o pierwszym lepszym użytkowniku z bazy

 

MongoDB + Spring Data

W poprzednim wpisie pokazałam, jak dodawać dokumenty i kolekcje do nierelacyjnej bazy MongoDB oraz jak połączyć się z tą bazą z kodu javowego.

W tym odcinku pokażę, jak łatwo utrwalić w MongoDB obiekty przetwarzane w aplikacji webowej.

Wpis w pom.xml

Kolejny raz korzystam ze „startera” Spring Boot, który grupuje w sobie kilka podstawowych bibliotek realizujących spójny cel – w tym wypadku mapowanie, zapis i odczyt danych z MongoDB:

Adnotacje w obiekcie, który ma zostać utrwalony w bazie danych

Do obiektu reprezentującego użytkownika bazy danych dodałam następujące adnotacje:

  • @Document oznacza, że mamy do czynienia z obiektem zapisywanym w bazie danych. Jest to odpowiednik @Entity w wersji Spring Data dla relacyjnych baz danych.
  • @Id to automatycznie generowany, obowiązkowy identyfikator dokumentu w ramach kolekcji w MongoDB.
  • Pola nieopisane zostaną bezpośrednio odwzorowane w dokumencie w bazie danych.
  • @DBRef oznacza odwołanie do innej kolekcji w bazie. Uwaga! Nie wolno tworzyć odwołań obustronnych, MongoDB brzydko się przez nie zapętli. Zamiast tego należy po jednej stronie jawnie zapisać identyfikator dokumentu.

Interfejs do obsługi repozytorium

Muszę tylko zasygnalizować, że będę chciała pobierać z bazy obiekty danego typu:

Użycie w kodzie: kontroler i widok

Kontroler

W celu przetestowania konfiguracji dodałam odwołanie do kolekcji użytkowników do mojego powitalnego kontrolera GreetingController:

Nazwa pierwszego napotkanego użytkownika zostanie dodana do modelu.

Widok

W powitalnym szablonie Thymeleaf dopisałam kod, który wyświetli nazwę odnalezionego w bazie danych użytkownika, o ile taki użytkownik istnieje:

Efekt

Wyświetlona informacja pobrana z bazy danych
Wyświetlenie informacji pobranej z bazy danych. Przecinek od czapy.

Zapis informacji w bazie

Mogę skorzystać ze sposobu pokazanego w poprzednim wpisie. Mogę też przygotować kod, który będzie wywoływany przy każdym uruchomieniu aplikacji:

Konfiguracja połączenia z bazą danych

Tym razem oparłam się na wartościach domyślnych. Nie wpisałam w application.properties żadnych danych na temat połączenia z bazą danych. Oczywiście musiałam uruchomić mój serwer MongoDB.

Wszystko zadziałało.

Mogę podejrzeć zastosowane wartości właściwości związanych z MongoDB dzięki dołączonej w pom.xml bibliotece Spring Boot Actuator. Pod adresem http://localhost:8080/configprops widzę m.in. takie dane:

Test to baza danych, którą utworzyłam na potrzeby poprzedniego wpisu.

Troubleshooting: co zrobiłam nie tak przy pierwszym podejściu?

  • Zapomniałam o wpisaniu @Autowired w kontrolerze w punkcie 4 (komunikat CrudRepository ... is no accessor method!)
  • Nie włączyłam MongoDB (Timed out after 10000 ms while waiting for a server that matches AnyServerSelector{} ... caused by {java.net.ConnectException: Connection refused: connect}})

Jak zacząć z MongoDB?

Wprowadzenie

Najwyższa pora dodać do mojej konkursowej aplikacji jakąś bazę danych. Od początku zakładałam, że z powodów edukacyjnych użyję bazy nierelacyjnej. Zdecydowałam się w końcu na MongoDB. Jest to jedna z popularnych baz nierelacyjnych obsługiwanych przez mojego dostawcę PaaS (Platform as a Service), tj. Pivotal Web Services.

Źródła

Swoją działalność w tym obszarze opieram na następujących źródłach:

Pierwsze kroki

Po instalacji serwera bazodanowego wykonuję następujące kroki:

  1. Uruchamiam proces serwera MongoDB:
  2. Przygotowuję rekord reprezentujący użytkownika mojego systemu (plik user.json). Rekordy w MongoDB mają postać dokumentów w standardzie JSON (w przybliżeniu). Wartościami pól mogą być również inne dokumenty oraz tablice (w tym tablice dokumentów). Dokumenty są przechowywane w ramach kolekcji. Kolekcja przypomina tablicę w relacyjnej bazie danych, tyle że zawarte w niej dokumenty wcale nie muszą mieć tej samej struktury. Dokumenty w kolekcji muszą mieć klucz główny o nazwie _id:
  3. Importuję rekord użytkownika do bazy danych:
  4. Dodaję sterownik MongoDB jako zależność w mavenowym pliku pom.xml.
  5. Łączę się z bazą danych z kodu w Javie:
  6. Wyszukuję użytkowników:
  7. Zmieniam wartość jednego z pól użytkownika (dokument d pełni tu rolę wzorca, według którego odnajdywany jest rekord z bazy):
  8. Nakładam indeks na nazwy użytkowników:

No i działa 🙂

Cały plik jest dostępny w GitHub.

Co dalej?

  1. Na serio dodać to do aplikacji 🙂
  2. Zastanowić się nad użyciem Hibernate.
  3. Zastanowić się, jak przechowywać zdjęcia.