Moje odkazy
Obsah článku:
vydáno: 4. 3. 2010 17:13, aktualizováno: 22. 9. 2014 16:54
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.
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 "6. díl"
Nejprve si v databázi vytvoříme tři nové tabulky:
CREATE TABLE uzivatel (
id
INTEGER
NOT NULL
DEFAULT nextval('uzivatel_seq'::regclass)
PRIMARY KEY,
prezdivka
CHARACTER VARYING(64)
NOT NULL -- Uživatelské jméno
UNIQUE,
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()
)
…
CREATE TABLE "role" (
kod character varying(16) NOT NULL,
popis character varying(255),
CONSTRAINT skupina_pk PRIMARY KEY (kod)
)
…
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
)
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 nemaí (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).
Po vytvoření tabulek k databázi přistoupíme k nastavení aplikačního serveru – samotnou aplikaci nebude potřeba měnit.
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:
jdbcRealm
jdbc/nekurak
uzivatel
prezdivka
heslo
uzivatel_role
role
SHA-512
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.
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:
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 taleko 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.
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.
Témata: [počítačová bezpečnost] [Java]
Tento článek zatím nikdo nekomentoval