Autentifikácia a autorizácia v servletových kontajneroch

2008/03/27

Úvod

Autentifikácia a autorizácia je súčasťou skoro každej významnej webovej aplikácie. Každý vývojar si tieto prvky zavádza do systému viacerými spôsobmi - buď vyvinutím vlastnej verzie alebo použitím niektorých z existujúcich riešení (napr. Acegi Security).

Jestvujú však prípady, keď si vystačíme s autentifikáciou a autorizáciou, ktorú poskytuje priamo špecifikácia servletov a implementujú ich jednotlivé servletové kontajnery.

V článku si ukážeme príklad jednoduchej aplikácie, ktorú zabezpečíme a nasadíme v kontajneroch Tomcat a Jetty.

Vytvorenie servletov

Webová aplikácia bude minimalistická. Budú ju totiž tvoriť len dva servlety: jeden reprezentujúci verejne dostupnú zónu a iný predstavujúci zónu určenú len pre správcov.

package webapp;
/* .. importy .. */
public class PublicServlet extends HttpServlet {

  @Override
  protected void doGet(HttpServletRequest request,  
                       HttpServletResponse response) 
  throws ServletException, IOException
  {
    PrintWriter out = response.getWriter();
    out.println("<h1>Public zone</h1>");
    out.println("<p>This is a public servlet</p>");
    out.flush();
  }  
}

a

package webapp;
/* .. importy .. */

public class PrivateServlet extends HttpServlet {

  @Override
  protected void doGet(HttpServletRequest request, 
                       HttpServletResponse response) 
  throws ServletException, IOException 
  {
    PrintWriter out = response.getWriter();
    out.println("<h1>Private zone</h1>");
    out.println("<p>This is a servlet in a private zone</p>");
    out.flush();
  }  
}

Oba servlety namapujeme klasickým spôsobom vo web.xml. Dohodneme sa, že všetky URL pod „adresárom" /private budú mapované na PrivateServlet a adresy pod /public bude obsluhovať PublicServlet.

Do web.xml teda pridáme:

<servlet>
  <servlet-name>PublicServlet</servlet-name>
  <servlet-class>webapp.PublicServlet</servlet-class>
</servlet>

<servlet>
  <servlet-name>PrivateServlet</servlet-name>
  <servlet-class>webapp.PrivateServlet</servlet-class>
</servlet>  

<servlet-mapping>
  <servlet-name>PublicServlet</servlet-name>
  <url-pattern>/public/*</url-pattern>
</servlet-mapping>


<servlet-mapping>
  <servlet-name>PrivateServlet</servlet-name>
  <url-pattern>/private/*</url-pattern>
</servlet-mapping>  

Základná idea spočíva:

Všetky tieto náležitosti možno nakonfigurovať v súbore web.xml.

Deklarácia používateľských rolí

V každej webovej aplikácii potrebujeme predovšetkým nadeklarovať všetky používateľské roly, ktoré sa budú používať. Na to slúži element <security-role>:

<security-role>
  <role-name>ADMIN</role-name>
</security-role>

Tento element zavedie rolu ADMIN. V prípade, že chceme zaviesť aj ďalšie roly, musíme ich uviesť vo viacerých elementoch <security-role> idúcich po sebe.

<security-role>
  <role-name>ADMIN</role-name>
</security-role>
<security-role>
  <role-name>MANAGER</role-name>
</security-role>

Určenie zabezpečených častí aplikácie a oprávnené roly

Na vymedzenie zabezpečených častí webovej aplikácie slúži element <security-constraint> a jeho podelementy. Do web.xml dodáme nasledovný element:

<security-constraint>
  <web-resource-collection>
    <web-resource-name>Private Zone</web-resource-name>
    <url-pattern>/private/*</url-pattern>
  </web-resource-collection>
  <auth-constraint>
    <role-name>ADMIN</role-name>
  </auth-constraint>        
</security-constraint>

V ňom sme určili, že:

Spôsob autentifikácie

Už vieme, ktoré zóny webaplikácie sú bezpečné a vieme, ktorých používateľov do nich môžeme vpustiť. Chýba nám však nastavenie spôsobu, ktorým sa môžu používatelia do systému autentifikovať. V servletových kontajneroch máme k dispozícii 4 spôsoby:

HTTP Basic autentifikácia

Pre jednoduchosť si uvedieme príklad najjednoduchšej Basic autentifikácie. Tú možno dosiahnuť dodaním elementu <login-config> do web.xml, pričom v podelemente <auth-method> uvedieme BASIC.

<login-config>
  <auth-method>BASIC</auth-method>	
  <realm-name>Admin</realm-name>	
</login-config>

Element <realm-name> určuje názov zóny, do ktorej sa chceme autentifikovať. Môže byť ľubovoľný, ale vzhľadom na to, že sa zobrazí v prihlasovacom okne v prehliadači, je vhodné ho nastaviť na nejaký prehľadný text.

Konfigurácia mien a hesiel

Uvedením predošlých krokov sme ukončili konfiguráciu vo web.xml. Natíska sa však otázka, že kde vlastne nastavíme zoznam používateľov a ich heslá? To je však oblasť, ktorú špecifikácia servletov už nerieši a ponecháva ju na jednotlivých implementátorov servletových kontajnerov.

Konfigurácia mien a hesiel v Jetty

V kontajneri Jetty to dosiahneme výberom vhodnej implementácie zóny (realm). K dispozícii máme tri zabudované:

Voľbu implementácie uvedieme do konfiguračného súboru príslušnej webovej aplikácie. Štandardne je to XML súbor v adresári contexts v inštalačnom adresári Jetty. V najjednoduchšom prípade vyzerá súbor nasledovne:

<?xml version="1.0"  encoding="ISO-8859-1"?>
<!DOCTYPE Configure 
  PUBLIC "-//Mort Bay Consulting//DTD Configure//EN" 
  "http://jetty.mortbay.org/configure.dtd">

<Configure class="org.mortbay.jetty.webapp.WebAppContext">
  <Set name="contextPath">/secureapp</Set>
  <Set name="resourceBase">c:/java/secureapp/web</Set>
</Configure> 

Načítavanie mien a hesiel zo súboru

Ak sa rozhodneme pre HashUserRealm, do elementu <Configure> dodáme element:

<Get name="securityHandler">
  <Set name="userRealm">
    <New class="org.mortbay.jetty.security.HashUserRealm">
      <Set name="name">Admin</Set>
      <Set name="config">
        c:/java/secureapp/web/WEB-INF/realm.properties
      </Set>
    </New>
  </Set>
</Get>

Vytvorili sme teda zónu, ktorá načítava mená a heslá z textového súboru realm.properties v danom adresári. Podotknime, že názov zóny (teda Admin) v atribúte name sa musí zhodovať s názvom zóny (realm-name) z web.xml.

Súbor obsahuje položky v tvare

login:heslo,rola1,rola2,...

Ak chceme pridať používateľa turing25 s heslo tm a priradiť mu rolu ADMIN, dodáme riadok:

turing25: tm,ADMIN

Reštartneme servletový kontajner a môžeme si vyskúšať zabezpečenú webovú aplikáciu.

Načítavanie mien a hesiel z relačnej databázy

Mená a heslá môžeme tiež načítavať z relačnej databázy. Predstavme si jednoduchú dátovú štruktúru:

<Get name="securityHandler">
  <Set name="userRealm">
    <New class="org.mortbay.jetty.security.JDBCUserRealm">
      <Set name="name">Admin</Set>
      <Set name="config">
          c:/java/secureapp/web/WEB-INF/jdbcRealm.properties
      </Set>
    </New>
  </Set>
</Get>

Oproti HashUserRealm sme zmenili len názov triedy a cestu ku konfiguračnému súboru. V ňom uvedieme nastavenia pre pripojenie k databáze:

  1. názov triedy ovládača

    jdbcdriver = com.mysql.jdbc.Driver
    
  2. URL k ovládaču

    url = jdbc:mysql://localhost/security
    
  3. prihlasovacie meno k databáze

    username = root
    
  4. heslo k databáze

    password = root
    
  5. názov tabuľky s používateľmi

    usertable = user
    
  6. primárny kľúč

    usertablekey = id
    
  7. stĺpec s menom používateľa

    usertableuserfield = username
    
  8. stĺpec s heslom

    usertablepasswordfield = password
    
  9. názov tabuľky s rolami

    roletable = role
    
  10. primárny kľúč

    roletablekey = name
    
  11. stĺpec s názvom roly

    roletablerolefield = name
    
  12. názov medzitabuľky používatelia-roly

    userroletable = user_roles
    
  13. cudzí kľúč s používateľom

    userroletableuserkey = user_id
    userroletablerolekey = role
    
  14. počet sekúnd, počas ktorých cacheovať načítané údaje

    cachetime = 300
    

Potrebujeme ešte získať JDBC driver k príslušnej databáze. V našom prípade používame MySQL ovládač. JAR knižnicu skopírujeme do adresára lib v inštalačnom adresári Jetty.

Konfigurácia mien a hesiel v Tomcate

Podobne ako Jetty sú v Tomcate k dispozícii viaceré implementácie pre zónu (realm):

Používanú implementáciu zóny nastavíme v konfiguračnom súbore webovej aplikácie. Štandardne je to súbor názovAplikácie.xml umiestnený v adresári Catalina/localhost v inštalačnom adresári Tomcatu. Minimalistický konfiguračný súbor vyzerá nasledovne:

<Context docBase="D:/projects/tomcat-security/web">
</Context>

Použitú implementáciu zóny dodáme pomocou elementu <Realm>.

Načítavanie mien a hesiel zo súboru

<Context docBase="D:/projects/tomcat-security/web">
  <Realm className="org.apache.catalina.realm.MemoryRealm" 
         pathname="c:/java/secureapp/web/WEB-INF/users.xml" />
</Context>

Zadeklarovali sme teda zónu načítavajúcu používateľov z XML súboru, ktorého cestu sme uviedli do atribútu pathname. Ako vyzerá daný súbor users.xml? Jednoducho:

<?xml version='1.0' encoding='utf-8'?>
<tomcat-users>
  <user username="turing25" password="tm" roles="ADMIN"/>
</tomcat-users>

V nej sme pridelili používateľovi turing25 s heslom 25 rolu ADMIN. Ak má používateľ viacero rol, oddelíme ich čiarkou.

Následne reštartujeme server a naša aplikácia bude opäť zabezpečená.

Poznamenajme, že element <Realm> môžeme uviesť aj v rámci elementu <Engine>. V tom prípade budú nastavenia zóny platné pre všetkých virtuálnych hostiteľov a všetky webové aplikácie. Ak ho uvedieme v rámci <Host>, bude to platiť pre webaplikácie v rámci virtuálneho hostiteľa. Nastavenia na špecifickejšej úrovni prekrývajú globálnejšie nastavenia.

Načítavanie mien a hesiel zo relačnej databázy

Podobne ako v prípade Jetty je možnosť načítavať mená a heslá z relačnej databázy. Tomcat však vyžaduje inú štruktúru databázy:

<Realm className="org.apache.catalina.realm.JDBCRealm" debug="99"
       driverName="com.mysql.jdbc.Driver"
       connectionURL="jdbc:mysql://localhost/security"
       connectionName="root"
       connectionPassword="root"
       userTable="user" 
       userNameCol="username" 
       userCredCol="password"
         
       userRoleTable="user_roles2" 
       roleNameCol="role"
/>

Pred reštartovaním servera vložíme JAR s JDBC ovládačom do adresára lib v inštalačnom adresári Tomcatu.

Nevýhodou Tomcatu v tomto prípade je absencia chybových hlášok v prípade, že sa niečo pokazilo (napr. že chýba JDBC ovládač). To nie je veľmi prívetivé chovanie, ale dúfajme, že v ďalších verziách sa zlepší.

Autentifikácia pomocou HTML formulára

V príklade sme spomenuli autentifikáciu pomocou HTTP Basic. Ako alternatívu je možné použiť spôsob, v ktorom používateľ bude zadávať údaje do HTML formulára. Výhodou je možnosť prispôsobiť vzhľad prihlasovacej obrazovky zvyšku aplikácie.

Správanie v aplikácii bude nasledovné:

  1. používateľ navštívi stránku, ktorá požaduje autentifikáciu
  2. server presmeruje používateľa na prihlasovaciu stránku. URL stránky, ktorá požaduje prihlásenie si zapamätá.
  3. použivateľ sa prihlási vyplnením dát vo formulári
  4. ak sú prihlasovacie údaje správne, server ho presmeruje na pôvodnú stránku
  5. v opačnom prípade ho server presmeruje na chybovú stránku

Vytvorenie prihlasovacej stránky

Prihlasovacia stránka je klasická HTML stránka so špeciálnym formulárom: (:source lang=html:)

<h1>Prihlásenie do systému</h1>
<form method="POST" action="j_security_check">
  Meno: <input type="text" name="j_username"> <br />
  Heslo: <input type="password" name="j_password"> <br />
  <input type="submit" value="Prihlásiť sa" />
</form>

Na tejto stránke nie je nič význačné. Do pozornosti dávame akurát názvy políčok. Meno používateľa musí byť uvádzané do ovládacieho prvku s názvom j_username, heslo do prvku s názvom j_password. Formulár musí byť namapovaný na akciu j_security_check (po spustení sem server dosadí vhodnú URL adresu).

Nastavenie formulárovej autentifikácie

Ďalším krokom je zmena druhu autentifikácie vo web.xml. Element <login-config> upravíme nasledovne:

<login-config>
  <auth-method>FORM</auth-method>	
  <realm-name>Admin</realm-name>	
  <form-login-config>
    <form-login-page>/login.html</form-login-page>
  </form-login-config>
</login-config>

Zmenili sme teda auth-method na FORM a dodali sme element <form-login-config>. V ňom sme nastavili adresu k stránke, ktorá bude slúžiť na prihlásenie.

Ak sa prihlásiť nepodarí, zobrazí sa chybová stránka, ktorej vzhľad je závislý na serveri (a teda je často neprívetivý). Preto je lepšie nastaviť si vlastnú chybovú stránku, čo dosiahneme dodaním elementu <form-error-page> pod <form-login-config>:

<form-login-config>
  <form-login-page>/login.html</form-login-page>
  <form-error-page>/error.html</form-error-page>
</form-login-config>

Ostáva si vytvoriť príslušnú HTML stránku. Uložíme ju error.html do adresára web v adresári webaplikácie.

<h1>Chyba pri prihlásení!</h1>
Zadali ste nesprávne meno alebo heslo.

Programová kontrola autorizácie

Niekedy je potrebné, aby sme pristupovali k prihlasovacím informáciám priamo zo servletu. Chceli by sme napríklad vedieť, či je používateľ prihlásený a pod akým menom a prípadne rolu, v ktorej vystupuje. Trieda HttpServletRequest dáva k dispozícii dve významné metódy:

Referencie

>> Home