Java na webovém serveru: autorizace a autentizace

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.

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

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ého proměnného 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/au­tentizaci 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 jeho 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í:

Java - autentizace

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:

Java - autentizace

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ě – jedná se tedy o stejnou výhodu jako u datových zdrojů definovaných na úrovni aplikačního 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.

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.

Na rozdí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_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ě.

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

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

Přehled komentářů

První únik
uf prima, dik
piskot Sifrovani?
Palo Re: Sifrovani?
František Kučera Re: Sifrovani?
uf Re: Sifrovani?
b*d Re: Sifrovani?
František Kučera Re: Sifrovani?
v6ak CSRF?
MeDon DeclareRoles
Zdroj: https://www.zdrojak.cz/?p=3177