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ů)
- Java na serveru: úvod 8. 1. 2010
- Java na webovém serveru: první web 15. 1. 2010
- Java na webovém serveru: práce s databází 29. 1. 2010
- Java na webovém serveru: práce s databází II 12. 2. 2010
- Java na webovém serveru: lokalizace a formátování 19. 2. 2010
- Java na webovém serveru: autorizace a autentizace 26. 2. 2010
- Java na webovém serveru: autorizace a autentizace II 5. 3. 2010
- Java na webovém serveru: porovnání Javy a PHP 10. 3. 2010
- Java na webovém serveru: Vlastní JSP značky a servlety 17. 3. 2010
- Java na webovém serveru: posílání e-mailů a CAPTCHA 24. 3. 2010
- Java na webovém serveru: píšeme REST API 7. 4. 2010
- Java na webovém serveru: SOAP webové služby 14. 4. 2010
- Java na webovém serveru: hlasování a grafy v SVG 28. 4. 2010
- Java na webovém serveru: Komentáře a integrace s Texy 9. 6. 2010
- Java na webovém serveru: AJAX formuláře 23. 6. 2010
- Java na webovém serveru: implementujeme Jabber 30. 6. 2010
Nálepky:
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/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 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í:
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ě – 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&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
- Security Annotations and Authorization in GlassFish – více k anotacím deklarujícím zabezpečení.
- Java Authentication and Authorization Service – záznam na Wikipedii o technologii JAAS.
- Java Authorization Contract for Containers – o JACC od Sunu (Oraclu).
- Odhlášení z HTTP autentizace – starší článek na Intervalu.
„třeba uděláte alternativní verzi pro wap a… bavíme se tím dodnes :-)“
+1 :-D
Od vikendu to projedu postupne cely serial
Dekuji za clanek – serial.
Zajimalo by me, jak zabezpecit heslo – aby se posilalo sifrovane (hash). Bude nekdy rec i o https?
1) To su zname principy ktore nesuvisia nijako s Javou maximalne Javascript.
2) Ak chcete mat prihlasovaci formular tak proste uzivatela presmerujte na prihlasovaciu stranku ktora je v HTTPs a tam daje obycajny submitovaci formular. Heslo vam pride cez kryptovany kanal takze sa neda odsledovat cestou po sieti ale server ho vidi cleartext. Hned pri vstupe do servra ho HASHujte (sha256). A potom mozete porovnavat a ukladat napriklad do databazy.
3) Cela problematika je radovo vacsia a komplikovanejsia. Mozete pouzivat rozne druhy prihlasovania (Basic, Form, Token, …). Mozete pouzivat kryptovanie cez HTTPS ale iba po predradeny server ktory koli vykonu zabezpecuje kryptovanie a dalej je uz nekryptovane. Mozete heslo porovnavat voci DB, LDAP-u inemu centralnemu ulozisku. Uxistuju frameworky ktore vam mozu pomoct. Jednak standardny JAAS alebo napriklad spring-security (acegi). Bezpecnost v standardizovanom WEB containeri. No a k tomu este mate specifika pre rozne komerne a free aplikacne servre.
Takze si asi budete musiet nastudovat nejaky poriadne dlhy manual.
O hashování hesla na straně klienta jsem psal třeba tady: Ověřování uživatelů na webu. Je to i se zdrojákem, ale v PHP. Přiznám se, že v Javě jsem se tímhle zatím nezabýval – ono stejně, když to člověk myslí s bezpečností aspoň trochu vážně, tak je SSL nutností a pak už to hashování na straně klienta nemá takový efekt (víc práce než užitku). V Javě by to bylo potřeba řešit asi přes nějaký filtr nebo servlet.
Ad „Bude nekdy rec i o https?“
Nastavení HTTPS se liší server od serveru. Záleží taky, zda je javovský server přímo přístupný klientům, nebo je před ním ještě nějaká reverzní proxy (potom se SSL nastavuje na ní). Ve web.xml se dá šifrovaný protokol vynutit pomocí
Ale k tomu je potřeba, aby AS věděl, zda se šifrování používá nebo ne – což při prostém nasazení reverzní proxy (třeba apache nebo nginx) neví – ServletRequest.isSecure() bude vracet false, protože aplikační server komunikuje s proxy nešifrovaně. Jde to ošetřit přes „ventily“ (org.apache.catalina.Valve)
http://code.google.com/p/xebia-france/wiki/SecuredRemoteAddressValve
Ale vyresene to po tech letech v Jave asi je, ne?
Když použijete spring-security, tak si můžete kanál vynutit. Například jenom pro přihlášení nebo „přihlášený“ pouze https atp. Pak další bezpečnostní prvky jako je kopírování session, tj. zneplatnění té, kde jste byl anonimní je otázka jednoho řádku.
On se spíš ptal na ten CRAM, ne? To vynucení šifrovaného kanálu jde i bez Springu tou konfigurací ve web.xml.
Bude řeč i o Cross-site Request Forgery?
Nechybi tam @DeclareRoles? K cemu @DeclareRoles vubec je? :-)