Zapamiętywanie i walidacja danych w formularzu logowania GWT + JAAS

Widząc rosnące zainteresowanie tematyką bezpieczeństwa w GWT, postanowiłem jak najszybciej dostarczyć Ci kolejną przydatną dawkę informacji. Pamiętasz pewnie wpis o uwierzytelnianiu i autoryzacji w GWT – powstał wtedy mały projekt pokazujący, w jaki sposób połączyć aplikację napisaną w GWT z autoryzacją poprzez mechanizm Javy, a dokładniej użyliśmy JAAS (Java Authentication and Authorization Service). Tamten projekt miał jednak pewne wady, m. in. brak walidacji danych na formularzu logowania oraz nie obsługiwał zapamiętywania tych danych przez przeglądarkę. Pierwszy problem wynikał z użycia przycisku „submit”, który automatycznie wysyłał formularz, nie było więc momentu na sprawdzenie poprawności wysyłanych danych. Natomiast drugi problem jest bardziej złożony i związany z naturą samego GWT. W poszukiwaniu rozwiązania będziemy więc kontynuowali rozpoczęty wcześniej projekt.

Przeszukałem najpierw zasoby internetu, aby sprawdzić, czy występują już jakieś znane rozwiązania wyżej wymienionych problemów. Praktycznie wszystkie przykłady implementacji formularza logowania używają GWT RPC do przesyłania danych, więc nie było w czym przebierać. Jeśli zaś chodzi o zapamiętywanie danych logowania, problemem jest brak bezpośredniej możliwości skorzystania z mechanizmów przeglądarki, bo do tego potrzebny jest formularz wraz z polami loginu i hasła oraz przyciskiem „submit”, który musi znajdować się bezpośrednio w pliku html, a żaden z jego elementów nie może być wstrzykiwany javascript’em. Wszystkie moduły GWT są wstrzykiwane javascript’em w treść strony, dlatego przeglądarka nawet nie proponuje możliwości zapamiętania danych. Natknąłem się na dwa główne rozwiązania problemu:

  • zapamiętywanie danych logowania w ciasteczkach (łącznie z hasłem!),
  • wrzucenie odpowiedniego ukrytego formularza do pliku html modułu, z którego pobierane będą dane do pól utworzonych w GWT, a przy wysyłania danych przez GWT RPC, wstrzykiwanie tych danych z powrotem do ukrytego formularza.

Pierwszy sposób z góry odpada ze względów bezpieczeństwa: ciastka mogą być odczytywane przez inne strony otwarte w przeglądarce, które mogą też odczytać hasło (bądź hash’a, ale to i tak niebezpieczne). Drugi sposób nie działa na niektórych przeglądarkach. Nie sprawdzałem tego sam, ale ponoć nie chodzi na Chrome i Safari, może też na innych i jest samo z siebie rozwiązaniem trochę „na około”.

Walidacja danych

Rozwiązanie okazało się prostsze niż myślałem. Wystarczył zwykły ClickHandler na przycisku, w którym przerywamy natywne zdarzenie przeglądarki w przypadku błędu walidacji:

submitBtn = new SubmitButton("Zaloguj");
submitBtn.addClickHandler(new ClickHandler() {

  public void onClick(ClickEvent event) {
    if (!isFormValid()) {
      event.preventDefault();
      setErrorMessage("Musisz podać login i hasło!");
    }
  }
});

No i oczywiście metoda sprawdzająca poprawność danych:

private boolean isFormValid() {
  return loginField.getValue().length() > 0 && passwordField.getValue().length() > 0;
}

Na początek wystarczy sprawdzenie, czy zostały wpisane jakiekolwiek dane. Nie chodzi mi tutaj o jakieś dodatkowe sprawdzanie długości, czy poprawności wpisanych znaków, tylko o to, czy da się w ogóle to zrobić.

Zmiany te zostały przeprowadzone w pliku LoginModule.java. Inne modyfikacje w pliku nie są ważne z punktu widzenia problemu, a kod cały źródłowy będzie tradycyjnie zamieszczony na końcu artykułu.

Zapamiętywanie danych

W zasadzie tutaj poszło równie szybko :-) Zmodyfikowałem plik login.html dodając w nim wymagany przez przeglądarki formularz:

<form method="post" action="j_security_check">
  <div id="login-panel">
    <input id="form-login-field-username" type="text" name="j_username" />
    <input id="form-login-field-password" type="password" name="j_password" />
    <button id="form-login-submit" type="submit">Zaloguj</button>
  </div>
</form>

Dzięki nadanym tu identyfikatorom dla pól z nazwą użytkownika i hasłem oraz dla przycisku submit, mogłem w łatwy sposób odwołać się do tych elementów w module GWT. Zamiast korzystać z konstruktorów dla klas pól tekstowych i przycisku, skorzystałem ze statycznej metody „wrap” opakowującej element html obiektem w GWT.

loginField = TextBox.wrap(Document.get().getElementById("form-login-field-username"));
passwordField = PasswordTextBox.wrap(Document.get().getElementById("form-login-field-password"));
submitBtn = SubmitButton.wrap(Document.get().getElementById("form-login-submit"));

Nadawanie tym obiektom nazw (j_username i j_password) również stało się zbędne, gdyż występują one w html’u. Dzięki tak prostemu zabiegowi przeglądarka zaczęła wyrażać chęć zapamiętywania danych. Niewątpliwą zaletą tego rozwiązania jest też to, że chodzi na wszystkich popularnych przeglądarkach (sprawdziłem na IE, Firefox’ie, Operze i Chrome).

 

Jako bonus dodałem też w pliku pom.xml konfigurację Jetty, dzięki czemu można przetestować działanie aplikacji po kompilacji do javascript’u. Przed uruchomieniem Jetty ze skompilowaną aplikacją, otwórz plik jetty-web.xml (znajdujący się w katalogu src/main/webapp/WEB-INF) i odkomentuj linijki z adresami strony logowania i strony błędów bez parametru serwera trybu deweloperskiego GWT.

Pliki źródłowe projektu znajdują się do pobrania tutaj. Aplikację w trybie deweloperskim można uruchomić poleceniem „mvn gwt:run”, zaś skompilować i uruchomić w Jetty poleceniem „mvn jetty:run-war”.

Skomentuj


UWAGA - Możesz używać HTML tags and attributes:
<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

CommentLuv badge