Java na webovém serveru: autorizace a autentizace II

Dnes budeme pokračovat v tématu zabezpečení aplikace. Od ukládání uživatelů do souboru přejdeme k praktičtější autentizaci vůči databázi. A do naší aplikace doplníme registrační formulář pro nové uživatele.

Seriál: Java na webovém serveru (16 dílů)

  1. Java na serveru: úvod 8.1.2010
  2. Java na webovém serveru: první web 15.1.2010
  3. Java na webovém serveru: práce s databází 29.1.2010
  4. Java na webovém serveru: práce s databází II 12.2.2010
  5. Java na webovém serveru: lokalizace a formátování 19.2.2010
  6. Java na webovém serveru: autorizace a autentizace 26.2.2010
  7. Java na webovém serveru: autorizace a autentizace II 5.3.2010
  8. Java na webovém serveru: porovnání Javy a PHP 10.3.2010
  9. Java na webovém serveru: Vlastní JSP značky a servlety 17.3.2010
  10. Java na webovém serveru: posílání e-mailů a CAPTCHA 24.3.2010
  11. Java na webovém serveru: píšeme REST API 7.4.2010
  12. Java na webovém serveru: SOAP webové služby 14.4.2010
  13. Java na webovém serveru: hlasování a grafy v SVG 28.4.2010
  14. Java na webovém serveru: Komentáře a integrace s Texy 9.6.2010
  15. Java na webovém serveru: AJAX formuláře 23.6.2010
  16. Java na webovém serveru: implementujeme Jabber 30.6.2010

Uživatelé v databázi

Minule jsme pro jednoduchost uložili databázi uživatelů do souboru – použili jsme k tomu FileRealm. Jelikož je autentizační framework používaný v Javě modulární, snadno přenastavíme naši aplikaci, aby ověřovala uživatele vůči záznamům v databázi.

Jen pro připomenutí: jako obvykle si z Mercurialu stáhneme aktuální verzi zdrojových kódů k dnešnímu dílu seriálu:

$ hg pull
$ hg up "7. díl"

Potřebné databázové tabulky

Nejprve si v databázi vytvoříme tři nové tabulky:

CREATE TABLE uzivatel
(
  id integer NOT NULL DEFAULT nextval('uzivatel_seq'::regclass),
  prezdivka character varying(64) NOT NULL, -- Uživatelské jméno
  heslo character varying(512) NOT NULL,
  jmeno character varying(64),
  prijmeni character varying(64),
  email character varying(255),
  datum timestamp with time zone NOT NULL DEFAULT now(),
  CONSTRAINT uzivatel_pk PRIMARY KEY (id),
  CONSTRAINT uzivatel_prezdivka_uq UNIQUE (prezdivka)
)
…
CREATE TABLE uzivatel_role
(
  prezdivka character varying(64) NOT NULL,
  "role" character varying(16) NOT NULL,
  CONSTRAINT uzivatel_role_pk PRIMARY KEY (role, prezdivka),
  CONSTRAINT uzivatel_role_role_fk FOREIGN KEY ("role")
      REFERENCES "role" (kod) MATCH SIMPLE
      ON UPDATE CASCADE ON DELETE CASCADE,
  CONSTRAINT uzivatel_role_uzivatel_fk FOREIGN KEY (prezdivka)
      REFERENCES uzivatel (prezdivka) MATCH SIMPLE
      ON UPDATE CASCADE ON DELETE CASCADE
)
…
CREATE TABLE "role"
(
  kod character varying(16) NOT NULL,
  popis character varying(255),
  CONSTRAINT skupina_pk PRIMARY KEY (kod)
)

Kompletní definici tabulek naleznete ve zdrojových kódech aplikace. První tabulka obsahuje záznamy uživatelů, jejich hesla a profily. Druhá tabulka slouží k přiřazení rolí jednotlivým uživatelům. Třetí tabulka není nezbytně nutná, ale je vhodné si tento číselník rolí vytvořit. Jednak si k rolím můžeme doplnit vysvětlující poznámku a jednak nám pomůže referenční integrita v tom, aby se nám v aplikaci nevyskytovaly role, které tam být nemají (třeba v důsledku překlepů).

V naší aplikaci přiřazujeme každému uživateli roli bezny a budeme ji vyžadovat pro operace, které může provádět běžný přihlášený uživatel (např. přidávat nové podniky nebo psát komentáře). Pokud bychom někomu chtěli pozastavit účet, stačí mu v databázi tuto roli odebrat a tyto funkce nebude mít dostupné – opět výhoda deklarativního přístupu – nemusíme do kódu aplikace přidávat kontroly, zda uživatel nemá např. ve sloupečku neaktivni_ucet hodnotu true (zaručeně bychom na přidání kontroly alespoň na jednom místě zapomněli).

Úprava nastavení serveru

Po vytvoření tabulek k databázi přistoupíme k nastavení aplikačního serveru – samotnou aplikaci nebude potřeba měnit.

Java - autentizace 2

V Glassfishi jednoduše smažeme původní realm nekurakNET a vytvoříme nový se stejným jménem, tentokráte ale založený na třídě JDBCRealm a vyplníme tyto jeho parametry:

  • JAAS Context: jdbcRealm
  • JNDI: jdbc/nekurak
  • User table: uzivatel
  • User Name Column: prezdivka
  • Password Column: heslo
  • Group Table: uzivatel_role
  • Group Name Column: role
  • Digest Algorithm: SHA-512
  • Encoding: Hex

Většina parametrů asi nepotřebuje vysvětlení. Aplikační server se pokusí najít záznam pro danou přezdívku (uživatelské jméno) v tabulce uzivatel. Následně ověří heslo. A poté přiřadí úspěšně ověřenému uživateli role podle záznamů v tabulce uzivatel_role.

Hesla je samozřejmě možné uchovávat i v čistém tvaru (algoritmus: none), ale vhodnější bude je mít v databázi zahashovaná. Hashovací algoritmus si můžeme vybrat – MD5 (dosud je jako výchozí), nebo SHA-1, SHA-512 a další. Určíme kódování – jelikož výstupem hashovacího algoritmu je pole bajtů, převádějí se tyto bajty na text, který se snadněji ukládá do databáze. Kromě obvyklejšího hexadecimálního kódování můžeme použít i Base64.

Pokud zvolíme SHA-512 a Hex, budou hesla ve stejném tvaru, jaký můžeme získat v příkazové řádce pomocí:

echo -n "nějaké pěkné heslo" | sha512sum
7df16188f807fb…503e6e800fabeafc9bc1413d4dfe0a743e35ca -

Parametr -n je zde důležitý, protože příkaz echo jinak přidává nakonec řetězce znak konce řádku (hash by se tudíž neshodoval s tím, co očekává autentizační modul).

JNDI jméno odkazující na datový zdroj jsme použili stejné, jako používá aplikace. Ale není to pravidlem – např. můžeme chtít zabezpečit aplikaci tak, aby nemohla číst hesla uživatelů (ke své činnosti to totiž nepotřebuje), tudíž bychom si definovali dvě různá databázová spojení (pod různými DB uživateli), jedno pro autentizaci a druhé pro samotnou aplikaci.

Pokud nám standardní způsob ověřování nevyhovuje, můžeme si vytvořit vlastní autentizační modul a použít ho místo JDBCRealm. Např. bychom mohli uživatele ověřovat pomocí uložené databázové procedury (v tom případě by právo na SELECT hesel nemusel mít ani tento autentizační modul – pouze by zavolal proceduru s parametry jméno/heslo a ta by vrátila seznam rolí, kterými uživatel disponuje, nebo vyhodila výjimku při neúspěšné autentizaci). Výhodou je, že jakmile si jednou takový modul napíšeme, můžeme ho používat pro všechny své aplikace na daném serveru.

Zaujaly vás možnosti Javy a chcete se dozvědět o tomto jazyce víc? Akademie Root nabízí školení Základy programovacího jazyka Java a Pokročilejší kurz jazyka Java, na nichž se naučíte, jak tento multiplatformní objektově orientovaný jazyk používat.

Registrační formulář

Se záznamy uživatelů budeme v aplikaci pracovat stejně jako s jinými daty – nad datovou a EJB vrstvou si vytvoříme prezentační vrstvu (JavaBeany a JSP), formulář bude velice podobný tomu na přidávání podniků. Pouze doplníme ještě jeden krok:

  1. Uživatel vyplní registrační formulář a odešle ho.
  2. Zobrazíme mu zadané údaje a on je potvrdí.
  3. Po potvrzení teprve založíme účet.

Uživatel potvrzuje údaje kliknutím na odkaz na stránce. Neodesílá se podruhé formulář a místo toho odkaz obsahuje token (hash náhodného čísla). Data jsou mezitím uložena v objektu na straně serveru (se scope="session"). Jednak si uživatel může zkontrolovat vyplněné údaje a jednak to naši aplikaci ochrání před CSRF. V případě registračního formuláře je ochrana před tímto typem útoku možná zbytečná, ale až budeme psát funkci pro hlasování v anketách, bude se ochrana pomocí tokenu daleko víc hodit – Zabráníme tak útočníkovi ve zmanipulování ankety umístěním odkazu (např. obrázku) na nějakou hodně navštěvovanou stránku.

Pokud bychom k registračnímu formuláři doplnili CAPTCHu Mohli bychom hash v odkazu vypustit (CAPTCHa aplikaci ochrání i před CSRF).

V souboru registrovatUzivatele.jsp si inicializujeme tři objekty:

<jsp:useBean id="uzivatel" class="cz.frantovo.nekurak.dto.Uzivatel" scope="request"/>
<jsp:useBean id="uzivatelPredRegistraci" class="cz.frantovo.nekurak.web.UzivatelPredRegistraci" scope="request"/>
<jsp:useBean id="registraceUzivatele" class="cz.frantovo.nekurak.web.RegistraceUzivatele" scope="session"/>

Objekt uzivatel je uživatelský účet, který bude nakonec uložen do databáze. uzivatelPredRegistraci je instance pomocné třídy, která ho obaluje a stará se o generování náhodného kontrolního tokenu. Tyto dva objekty mají nastavenou platnost jen pro daný HTTP požadavek ( scope="request"). Třetí objekt RegistraceUzivatele je platný pro celou relaci uživatele ( scope="session") a to proto, aby udržel stav (údaje vyplněné do formuláře a nastavené objektu uzivatel), mezi HTTP požadavky (kontrola údajů a následně potvrzení registrace). Uživatel by si totiž mohl otevřít několik registračních formulářů, odeslat je a pak jeden z nich potvrdit.

Za zmínku stojí, že pokud v JSP vyplňujete vlastnosti nějakého objektu (v tomto případě uživatelského účtu) a názvy proměnných v Javě se shodují s názvy POST/GET parametrů odeslaných přes HTTP, není potřeba vlastnosti nastavovat po jedné a lze to udělat hromadně:

<jsp:setProperty name="uzivatel" property="*"/>

Pokud se v budoucnu rozhodneme podmínit registraci ověřením e-mailové adresy (v současnosti není ani povinná, stačí vyplnit přezdívku a heslo), stačí, když nebudeme uživatelům přiřazovat roli bezny automaticky hned po registraci a uděláme to až když uživatel klikne na odkaz v e-mailu. Tento systém je poměrně flexibilní a umožňuje měnit procesy na základě aktuálních potřeb.

Závěr

V dnešním díle jsme si ukázali, jak lze prakticky bez zásahu do aplikace změnit způsob ověřování uživatelů – ověřování oproti SQL databázi místo souboru (který se hodí spíše pro prototypování). A přidali jsme registrační formulář pro nové uživatele. Příště je na řadě trochu teoretičtější díl – porovnání výhod Javy a PHP na webovém serveru.

Odkazy

Franta Kučera působí jako Java vývojář na volné noze. Programování je jeho koníčkem už od dětství. Kromě toho má rád Linux, relační SŘBD a XML.

Komentáře: 12

Přehled komentářů

v6ak Token do URL nepatří!
František Kučera Re: Token do URL nepatří!
v6ak Re: Token do URL nepatří!
v6ak Bacha na obecné nastavování beans!
František Kučera Re: Bacha na obecné nastavování beans!
v6ak Re: Bacha na obecné nastavování beans!
kvr DB definice pro uživatele
František Kučera Re: DB definice pro uživatele
Tomáš J. Kouba Role
František Kučera Re: Role
Petr Realmy na hostinzích
baboon Nastavení realmu s jednou tabulkou
Zdroj: https://www.zdrojak.cz/?p=3182