Archiwa tagu: Thymeleaf

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}})

Pozbywam się powtarzalnych fragmentów HTML przy użyciu Thymeleaf

Każdy programista wie, że duplikacja w kodzie źródłowym to zło, gdyż:

  • jeśli zduplikowany kod zawiera błąd, trzeba naprawić go w kilku miejscach (i z reguły o którymś się zapomina),
  • długi kod trudniej się czyta.

A jednak zadanie przerzucenia powtarzanych fragmentów HTML (jak nagłówek i stopka wyświetlane na wszystkich podstronach) do osobnych, reużywalnych plików wcale nie jest trywialne!

Mój plik index.html w oryginalnej postaci zawierał 71 linii, z czego około 50 było wspólnych z innymi szablonami (nagłówek, stopka, menu, załączane skrypty js). Zależało mi na wyodrębnieniu części wspólnych do własnych plików. Chociażby po to, żeby dodanie pozycji z menu nie musiało być odwzorowywane w pięciu miejscach.

Jakie miałam możliwości?

  1. Oprogramować operację include w JavaScripcie (lub użyć czyjegoś kodu, np. z W3Schools). Wady:
    • dużo JavaScriptu,
    • metoda z gatunku nieeleganckich hacków.
  2. Użyć znaczników HTML: object, embed lub nawet iframe. Wady:
    • dwa pierwsze znaczniki zostały zaprojektowane do osadzania na stronach obiektach takich jak filmy, choć da się ich użyć tak że do wstawienia dokumentu HTML,
    • wszystkie służą do dodania do strony kompletnego dokumentu, a nie fragmentu,
    • styl CSS obowiązujący na stronie nie zadziała na załączone w ten sposób fragmenty.
  3. Skorzystać z mechanizmu udostępnianego przez silnik szablonów, w tym wypadku Thymeleaf. Wady:
    • serwer wykonuje pracę, którą mógłby wykonać klient 🙂
    • ewentualna zmiana silnika szablonów staje się jeszcze bardziej kosztowna.

Zdecydowałam się na rozwiązanie numer 3. Jak to wygląda w praktyce?

Na wszystkich moich stronach powtarzał się poniższy fragment:

Przeniosłam go (wraz z innymi podobnymi elementami) do osobnego pliku _menus.html:

Dzięki temu mogę załączać go wszędzie tam, gdzie jest potrzebny, w następujący sposób:

Pułapka 1: Na początku umieściłam atrybuty id i class we fragmencie załączanym (w _menus.html). Zostały zjedzone 🙂

A co, jeśli fragment, który chcę załączyć, nie jest otoczony sensowną parą znaczników? Mogę wtedy użyć bloku, np.:

I załączyć go w analogiczny sposób:

W obecnej chwili index.html ma tylko 32 niepuste linie i dużo zyskał na czytelności, podobnie jak inne szablony.

Pułapka 2: Po wyodrębnieniu stopki do osobnego pliku, moim oczom ukazał się taki oto widok:

Problem z kodowaniem załączonego pliku
Problem z kodowaniem załączonego pliku

Wszystkie pliki mam zakodowane w UTF-8, takie samo kodowanie deklaruję w nagłówku HTML. Poczytałam trochę o problemach z kodowaniem na linii Spring/Thymeleaf, ale ostatecznie, ponieważ na teraz jest to jedyne wystąpienie polskiego znaku na mojej stronie, po prostu zmieniłam odpowiadającą za to linię na:

 

 

 

 

Czasami coś idzie nie tak… Moja własna strona błędu

Nadal w ramach Daj się poznać.

Podobny obrazek widział chyba każdy programista aplikacji webowej. Od czasu do czasu serwer Tomcat postanawia poinformować użytkownika aplikacji, że coś poszło nie tak. Informuje dość brutalnie, przynajmniej od strony stylistycznej. Co gorsza, przy okazji potrafi wyjawić przypadkowemu użytkownikowi dość dużo informacji na temat wnętrza programu.

Informacja o błędzie wygenerowana przez serwer Tomcat. W tym wypadku żądana strona nie istnieje.
Informacja o błędzie wygenerowana przez serwer Tomcat. W tym wypadku żądana strona nie istnieje. Czasami można w tym miejscu zobaczyć ciekawszy materiał, czyli stack trace.

Tego typu informacje przydają się do debugowania aplikacji na etapie jej powstawania, ale użytkownik końcowy nie powinien ich oglądać (chociażby z powodów estetycznych).

Jak sobie z tym poradzić? Framework Spring Boot automatycznie ustawia dla nas nieco tylko ładniejszą stronę błędu:

Whitelabel Error Page, czyli strona błędu generowana przez Spring Boot
Whitelabel Error Page, czyli strona błędu generowana przez Spring Boot

Mechanizm ten można wyłączyć we właściwościach aplikacji (application.properties):

Także zmiana domyślnego ViewResolver może usunąć tę wersję informacji o błędzie i przywrócić wersję tomcatową.

Jeśli zamiast strony prezentowanej przez Springa chcesz ustawić swoją własną, musisz zerknąć do dokumentacji wybranego przez Ciebie silnika szablonów. W przypadku Thymeleaf wystarczy dodać szablon o nazwie error.html:

Szablon błędu dla silnika Thymeleaf
Szablon błędu dla silnika Thymeleaf

Dzięki temu strona błędu może wyglądać na przykład tak:

error

Czy jest ładniejsza… Pozostawiam ocenie Czytelnika 😀