FK~

Moje odkazy

Ostatní odkazy

Close Windows
Nenajdete mě na Facebooku ani Twitteru
Rozpad EU
Jsem členem FSF
There Is No Cloud …just other people's computers.
Sane software manifesto / Manifest příčetného softwaru

Java na serveru: autorizace a autentizace

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.

Logo OpenJDK (průhledné 1)

Pro začátek neuškodí zopakovat si dva základní pojmy – aneb „autorizace je když…“

  • Autentizace je operace, při které zjišťujeme totožnost subjektu.
  • Autorizace je operace, při které zjišťujeme, jestli je subjekt oprávněn k nějaké činnosti, např. přístup k  objektu.

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.

Možnosti zabezpečení webové aplikace

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.

Definice bezpečnostních omezení na webu

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.

Přihlašování a ověřování uživatelů

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í:

Vytvoření nové „říše“

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:

Založení uživatele

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.

Mapování rolí

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é).

HTTP vs. formulářová autentizace

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&amp;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_usernamej_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ě.

Odhlašová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.

Deklarativní bezpečnost

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še aplikace Nekuřák.net

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.

Závěr

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.

Odkazy a zdroje:

Témata: [počítačová bezpečnost] [Java]

Komentáře čtenářů

Tento článek zatím nikdo nekomentoval

Přidat komentář

reagujete na jiný komentář (zrušit)
jméno nebo přezdívka
název příspěvku
webová stránka, blog
e-mailová adresa
nápověda: možnosti formátování
ochrana proti spamu a špatným trollům

Náhled komentáře