Moje odkazy
Obsah článku:
vydáno: 25. 2. 2010 14:07, aktualizováno: 22. 9. 2014 16:54
Po předchozím díle o lokalizaci a formátování přistoupíme k dalšímu důležitému tématu. Tím je bezpečnost. Ukážeme si jak v Javě autorizovat a autentizovat uživatele a jak jim umožnit přístup jen tam, kam ho mít mají. Také naše výuková aplikace trochu pokročila – umí přidávat záznamy o podnicích do databáze.
Pro začátek neuškodí zopakovat si dva základní pojmy – aneb „autorizace je když…“
Abychom mohli rozhodnout, zda je subjekt oprávněn (autorizovat ho), musíme vědět, s kým máme tu čest – logicky tedy autorizaci předchází autentizace. Jako mnemotechnická pomůcka vám může posloužit: Autentizace – ptáme se: kdo to je? Odpověď je ten. Autorizace – ptáme se: co může dělat? Odpověď je to.
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"
Případně si je můžete stáhnout jako bzip2 archiv přes web.
Stejně jako v případě práce s databázemi i v oblasti bezpečnosti je více cest, jak na věc jít.
Můžete si vše řešit po svém, napsat si vlastní HTML přihlašovací formuláře,
odesílat jméno a heslo POSTem třeba na servlet nebo JSP stránku, v ní údaje ověřit,
nastavit jméno uživatele do nějaké proměnné sezení…
Také můžete svoji aplikaci zabezpečit pomocí Filtrů (javax.servlet.Filter
),
které aplikaci „překryjí“ a postarají se o ověření uživatelů a zabránění přístupu nezvaným hostům.
Jelikož ověřování uživatelů a věci s tím spojené musí řešit prakticky každá aplikace, nedává moc smysl, aby si je vývojář psal s každou aplikací znovu a znovu. Dnešní díl bude tedy o tom, co nám Java jako platforma nabízí a jak vyřešit autorizaci/autentizaci bez psaní zbytečného kódu (jen s trochou konfigurace).
Kromě ušetřeného kódu je hlavní výhodou tohoto přístupu modularita a pružnost. Chcete mít uživatele v LDAPu místo v databázi? Není problém, stačí upravit konfiguraci a do aplikace není potřeba zasahovat. Rozhodli jste se, že místo HTTP autentizace chcete používat HTML formuláře? Opět – jen dva řádky v konfiguračním souboru – aplikaci není potřeba měnit. Díky tomu se můžete soustředit na smysl vaší aplikace (čím bude užitečná svým uživatelům), zatímco režii a servisní záležitosti za vás bude řešit platforma.
Ve webové aplikaci si můžeme definovat místa (cesty),
které budou chráněné a přístupné jen vybraným uživatelům resp. uživatelským rolím.
V souboru web.xml
si zabezpečíme cestu
/sprava/*
,
což je místo, kam časem umístíme administrační rozhraní aplikace.
<security-constraint>
<web-resource-collection>
<web-resource-name>Správa Nekuřák.net</web-resource-name>
<url-pattern>/sprava/*</url-pattern>
</web-resource-collection>
<auth-constraint>
<role-name>opravneny</role-name>
</auth-constraint>
</security-constraint>
Říkáme zde, že autorizovaným k dané části webu je jen uživatel s rolí opravneny
.
Rozhraní pro správu jsme zabezpečili – aplikační server zjistí,
že uživatel nemá příslušnou roli a odepře mu přístup.
Jenže chudák uživatel zatím nemá jak tuto roli získat –
musíme mu umožňit prokázat svoji totožnost – autentizovat se.
Opět budeme upravovat konfiguraci ve web.xml
– doplníme:
<login-config>
<auth-method>BASIC</auth-method>
<realm-name>nekurakNET</realm-name>
</login-config>
Poznámka:
Všimněte si prosím, že definice omezení (security-constraint
)
a nastavení přihlašování ( login-config
) jsou dvě různé věci –
a do jisté míry na sobě
nezávislé.
Odkazujeme se zde na tzv. realm (doména, království, říše), což je databáze uživatelských jmen, hesel a skupin, do kterých uživatelé patří. Jedná se o „databázi“ v širším slova smyslu – může to být prakticky cokoli – soubor na disku, LDAP, relační databáze, PKI… můžeme si i implementovat vlastní.
Výše uvedený kousek XML aplikačnímu serveru říká:
„pokud neznámý uživatel přijde někam, kam nesmí (viz security-constraint
výše),
vyžádej si od něj jméno a heslo pomocí HTTP autentizace (BASIC
)
a pokus se ho ověřit“.
Jelikož odkazovaná doména zatím neexistuje, veškeré pokusy o přihlášení budou neúspěšné. Doménu si tedy definujeme v aplikačním serveru. V případě Glassfishe to lze i jednoduše přes webové rozhraní:
Použili jsme FileRealm
, což znamená,
že informace o uživatelích a skupinách budou uloženy v obyčejném souboru na disku.
Pro vývoj aplikace nám to v tuto chvíli postačí
a později se můžeme snadno „přepnout“ a uživatele ověřovat např. oproti databázi.
Pomocí „Manage Users“ si přidáme uživatele a nastavíme mu heslo:
Všimněte si přiřazení uživatelských skupin (v našem případě jedna: spravce
).
Skupiny můžeme definovat buď pro celou doménu, nebo pro jednotlivé uživatele.
Nastavovat úložiště uživatelů na úrovni aplikačního serveru místo v aplikaci má hned dvě výhody: jednak můžeme jednu doménu sdílet mezi více aplikacemi a jednak a můžeme aplikaci beze změn nasazovat na různé stroje a vždy se budou uživatelé ověřovat vůči správné doméně – tedy stejná výhoda jako u datových zdrojů definovaných na úrovni aplikačního serveru.
Po úspěšném ověření v doméně má uživatel přiděleny určité skupiny (v našem případě spravce
).
Na úrovni aplikace ale pracujeme s rolemi (v našem případě opravneny
).
Mapování ze skupin na role provedeme v souboru sun-web.xml
takto:
<security-role-mapping>
<role-name>opravneny</role-name>
<group-name>spravce</group-name>
</security-role-mapping>
V naší jednoduché aplikaci, kde mapujeme skupinu a roli 1:1, vám asi připadá, že je to práce navíc. Ale ve chvíli kdy budete potřebovat propojit složitější systémy, sdílet jednu doménu mezi více různými aplikacemi, pravděpodobně oceníte pružnost tohoto řešení. Na role se také odkazujete ze zdrojového kódu a určitě se vám je nebude chtít přejmenovávat a znovu kompilovat aplikaci. (zatímco upravit XML konfiguraci – na jednom místě – je hračka).
Poznámka: uživatelské role jsou důležité a nelze je brát jako „něco navíc“ –
uživatel, který se úspěšně prokáže svým jménem a heslem,
ještě nemá zaručeno, že se k chráněným zdrojům (v našem případě /sprava/*
) dostane –
pokud totiž nebude v příslušné skupině (a tím pádem nebude mít příslušnou roli),
obdrží od serveru odpověď
HTTP Status 403 - Access to the requested resource has been denied
ve chvíli, kdy se bude pokoušet dostat, kam nemá
(přestože jméno a heslo zadal správné).
Zatím jsme si ukázali jednoduchou HTTP autentizaci, což pro uživatele znamená, že jméno a heslo zadává do dialogového okna, které je součástí jeho prohlížeče. Na jednu stranu je to standardizované řešení, ovšem zase neumožňuje přizpůsobení – uživatel zadává údaje do nějakého šedivého okna, které navíc v každém prohlížeči vypadá jinak – proto často chceme, aby uživatel mohl údaje vyplnit do HTML formuláře, který je součástí naší stránky.
Narozdíl od jiných platforem to v Javě není žádná drastická změna –
nebudeme muset zahodit to, co jsme doteď napsali a nebudeme muset programovat nic navíc.
Pouze upravíme konfiguraci ve web.xml
na:
<login-config>
<auth-method>FORM</auth-method>
<realm-name>nekurakNET</realm-name>
<form-login-config>
<form-login-page>/?akce=prihlaseni</form-login-page>
<form-error-page>/?akce=prihlaseni&chyba=ano</form-error-page>
</form-login-config>
</login-config>
A přidáme HTML stránku, obsahující formulář.
V našem případě je začleňená do JSP skriptů, ale může to být úplně obyčejná (X)HTML stránka,
která obsahuje formulář se správně pojmenovanými políčky:
j_username
a j_password
.
<form method="post" action="j_security_check">
<fieldset>
<label>Jméno: <input type="text" name="j_username"/></label><br/>
<label>Heslo: <input type="password" name="j_password"/></label><br/>
<button value="submit">Přihlásit se</button>
</fieldset>
</form>
Všimněte si cíle, kam se formulář odesílá: action="j_security_check"
.
Žádný skript s tímto názvem ale nepíšeme –
odeslaný formulář si odchytí server a postará se o ověření uživatele.
Pomocí <form-error-page>
nastavíme stránku,
která se má zobrazit, pokud uživatel zadá špatné heslo.
Opět se může jednat o prostou HTML stránku.
V našem případě je to JSP, které uživateli znovu zobrazí přihlašovací formulář,
ale doplní k němu hlášku, že jeho předchozí pokus o přihlášení nevyšel.
Mezi metodami autentizace můžeme snadno přepínat podle toho, jak se nám to pro danou aplikaci hodí, nebo co požaduje zákazník. HTTP autentizaci oceníte hlavně v případě, že budete psát nějaké API a s vaší aplikací nebude komunikovat člověk, ale nějaký jiný program. Naopak pro většinu uživatelských aplikací asi sáhnete po formulářové autentizaci (což vám ale nebrání aplikaci začít vyvíjet s HTTP BASIC a HTML formuláře dodělat až časem).
Kromě těchto dvou metod se může uživatel prokazovat i klientským certifikátem, který se ověřuje vůči certifikační autoritě nastavené v příslušné doméně.
Při HTTP BASIC autentizaci je poněkud problematické odhlašování uživatele (spolehlivě funguje snad jen zavření prohlížeče). Formulářová autentizace nám oproti tomu nabízí snadnější odhlašování. Stačí v JSP stránce zneplatnit aktuální sezení:
<jsp:scriptlet>session.invalidate();</jsp:scriptlet>
Nebo můžete použít čistější řešení – ukončíte sezení pomocí servletu a následně přesměrujete na stránku, která uživateli řekne, že byl odhlášen.
Zatím jsme se pořád pohybovali v prezentační-webové vrstvě,
dokázali jsme uživateli zabránit v přístupu k určité části našeho webu (/sprava/*
),
ale to je trochu málo.
Co když programátor nebo kodér JSP stránek zapomene na nějaký if
?
Co když k
nově přidané stránce nepřidá patřičné kontroly?
Na bezpečnost bychom měli dbát hlavně v nižších vrstvách aplikace
a nespoléhat se jen na to, že jsme uživateli ten formulář nebo skript
znepřístupnili.
Tím se dostáváme k jedné z nejsilnějších zbraní Javy v této oblasti – k deklarativní bezpečnosti. Uživatel, kterého jsme ověřili na webu získal určité role a ty se nesou s jeho požadavky i do nižších vrstev aplikace – když voláme metody obchodní logiky.
V naší aplikaci máme např. EJB, které umožňuje zakládání nových podniků – obsahuje tuto metodu:
public void zalozPodnik(Podnik p) {
podnikDAO.uloz(p);
}
Prostým doplněním anotace ji ochráníme před neoprávněným přístupem:
@RolesAllowed("opravneny")
public void zalozPodnik(Podnik p) {
podnikDAO.uloz(p);
}
Tím zajistíme, že ji může volat pouze přihlášený uživatel, který disponuje rolí opravneny
.
Nemusíme psát žádné if (uzivatel.role == "…") { … } else { … }
.
Prostor pro možné chyby se tak výrazně zmenší.
I kdyby selhaly všechny ochrany a kontroly v
prezentační vrstvě
, k neautorizovanému volání metody nedojde – vyústí totiž ve vyvolání výjimky
EJBAccessException
.
Díky deklarativnímu zabezpečení můžete psát skutečně spolehlivé aplikace, které obstojí třeba i v bankovním prostředí. Ovšem nevykládejte si to špatně: zkazit se totiž dá cokoli a deklarativní přístup proto chápejte jako velmi dobrý předpoklad k vysoké bezpečnosti – nikoli jako podmínku dostačující.
Na adrese nekurak.net najdete aktuální verzi aplikace.
Můžete si vyzkoušet přihlašování a odhlašování.
Jméno je: zdrojak.root.cz
a heslo: heslo
.
Výpis podniků je přístupný všem.
Přidávat nové podniky může jen přihlášený uživatel.
Přidávejte záznamy do databáze dle libosti
(ale počítejte s tím, že je budu občas promazávat – aplikace ještě není v normálním provozu).
Schválně si vyzkoušejte přidat podnik, když nejste přihlášeni –
formulář je sice normálně přístupný (není totiž v /sprava/*
),
ale přidání záznamu se vám nepodaří právě díky @RolesAllowed("opravneny")
.
Validace v prezentační vrstvě zatím žádná není –
pokud se tedy pokusíte např. zadat jako číslo popisné písmenka,
dostanete obecnou chybu (500) bez dalšího vysvětlení.
Tato chyba je zachycena už na úrovni JSP (požadavek nedojde k databázi),
jelikož proměnná cisloPopisne
v třídě Podnik
je typu
int
.
Dnes jsme se naučili ověřovat uživatele pomocí HTTP BASIC i formulářové autentizace a ukázali jsme si výhody, které skýtá zabezpečení deklarované pomocí anotací. Přístě se naučíme ověřovat uživatele vůči databázi a LDAPu. A taky si řekneme něco málo k EJB, ke kterým jsme se zatím nedostali.
Témata: [počítačová bezpečnost] [Java]
Tento článek zatím nikdo nekomentoval