Java na web X. - Autentizace a autorizace

Webová aplikace potřebuje své uživatele a mezi nimi je potřeba nějakým způsobem rozlišovat. V dnešním díle se budeme věnovat dvoum pojmům – autentizaci a autorizaci.

16.7.2013 12:00 | Petr Horáček | přečteno 10441×

V minulém díle jsme si aplikaci rozšířili o databázi, dnes přijde na řadu správa uživatelů a jejich přístupů. Nejdříve se seznámíme se základními pojmy, poté si popíšeme konfiguraci autentizace a autorizace, nakonec si opět vylepšíme naši aplikaci.

Autentizace a autorizace

Tyto dva pojmy sice vypadají velmi podobně, ale je velmi důležité si je neplést. Autentizace označuje zjištění identity uživatele, nejčastěji pomocí přihlášení. Autorizace určuje, které akce bude moci uživatel vykonat a které nikoliv.

Ukládání uživatelů

Asi bude dobré začít u samotného „skladování“ uživatelů, pro tyto účely můžeme použít například XML soubor nebo SQL databázi. V obou případech musíme nakonfigurovat tzv. Realm odkazující na Resource.

Realm (česky říše) je seznam všech uživatelů a skupin náležících k jedné aplikaci (nebo skupině aplikací).

UserDatabaseRealm

Při použití XML si ulehčíme práci spojenou s konfigurací databáze, pro pár stálých uživatelů (například správce) je toto řešení dostačující, hlavní nevýhoda tkví v nutnosti restartování aplikace pro načtení nově zapsaných uživatelů.

Konfigurace UserDatabaseRealm se nachází v souboru Tomcatu server.xml hned po instalaci (pozn.: lze ji ale vložit i do souboru context.xml aplikace). Skládá se ze dvou částí, první je Resource (vnořený do tagu GlobalNamingResources) odkazující na soubor conf/tomcat-users.xml ve složce Tomcatu.

<Resource auth="Container" description="Databáze uživatelů" 
    factory="org.apache.catalina.users.MemoryUserDatabaseFactory" 
    name="UserDatabase" 
    pathname="conf/tomcat-users.xml" type="org.apache.catalina.UserDatabase"/>

Druhou částí je tag Realm (vnořený do tagu <Realm className="org.apache.catalina.realm.LockOutRealm">) odkazující na zdroj (Resource) a definující název pomocí kterého budeme k uživatelům přistupovat.

<Realm className="org.apache.catalina.realm.UserDatabaseRealm" resourceName="UserDatabase"/>

Obsah souboru tomcat-users.xml má standardně zakomentován všechno nastavení, po úpravě může vypadat například takto:

<?xml version='1.0' encoding='utf-8'?>
<tomcat-users>
  <role rolename="role1"/>
  <user username="uzivatel" password="heslo" roles="role1"/>
</tomcat-users>

Tagem role vytvoříme novou roli, v tagu user vytvoříme nového uživatele, přiřadíme mu uživatelské jméno, heslo a roli.

JDBCRealm

V případě použití databáze pro ukládání uživatelů nás sice čeká poněkud delší příprava, poté ale budeme moci snadno a dynamicky spravovat více uživatelů.

Začněme s tím, jak by měla vypadat databáze pro ukládání uživatelů a rolí. a) Pro naše účely je potřeba alespoň jedna tabulka (může se nacházet ve společené databázi s dalšími daty aplikace) se jménem, heslem a rolí uživatele, řešení s jednou tabulkou nám ale dovoluje přiřazení jediné role na uživatele. b) Lepším řešením je vytvoření dvou tabulek. V první z nich budeme ukládata uživatele a hesla, v druhé budeme párovat uživatele s rolemi.

Když už máme databázi vytvořenou, můžeme přikročit ke konfiguraci Realm-u, ten je stejně jako UserDatabaseRealm možné vložit do souborů context.xml i server.xml.

a) Můžeme buď nakonfigurovat Realm a Resource v jednom tagu:

<Realm className="org.apache.catalina.realm.JDBCRealm"
      driverName="org.gjt.mm.mysql.Driver"
      connectionURL="jdbc:mysql://localhost/databaze?user=uzivatel&amp;password=heslo"
       userTable="users" userNameCol="username" userCredCol="password"
       userRoleTable="user_roles" roleNameCol="rolename"/>

b) Pokud ale už máte Resource s přístupem k databázi vytvořený, je možné se na něj z Realm-u pouze odkázat:

<Realm className="org.apache.catalina.realm.DataSourceRealm"
   dataSourceName="jdbc/mysql"
   userTable="users" userNameCol="username" userCredCol="password"
   userRoleTable="user_roles" roleNameCol="rolename"/>

Šifrování

Hesla by neměla být ukládána jako prostý text, ale v šifrované podobě. Tomcat standardně podporuje tři hashovací algoritmy: SHA, MD2 a MD5. Pro jejich použití stačí do tagu Realm přidat atribut digest a jako jeho parametr dosadit název algoritmu (např. digest="MD5").

Autorizace

Nyní zpět k autorizaci, ta se v Tomcatu konfiguruje jako omezení přístupu k jednotlivým servletům pouze pro určité role.

web.xml

První možností konfigurace autorizací je pomocí tagu security-constraintv souboru web.xml aplikace:

<security-constraint>
    <display-name>Private Security Constraint</display-name>
    <web-resource-collection>
        <web-resource-name>Protected Area</web-resource-name>
        <url-pattern>/private/*</url-pattern>
        <url-pattern>/administration</url-pattern>
        <http-method>GET</http-method>
        <http-method>POST</http-method>
    </web-resource-collection>
    <auth-constraint>
        <role-name>user</role-name>
        <role-name>admin</role-name>
    </auth-constraint>
</security-constraint>

Tag url-pattern zde určuje adresy, ke kterým se toto opatření vztahuje. Http-method určuje HTTP metody na kterých se má ochrana používat (pokud nazadáte žádnou, bude použita na všechny). Role-name určuje role, které mají povolený přístup. Pokud chcete používat na chráněných místech SSL protokol, můžete do security-constraint přidat tyto tagy:

<user-data-constraint>
    <transport-guarantee>CONFIDENTIAL</transport-guarantee>
</user-data-constraint>

Anotace

Druhou možností konfigurace autorizací je definování autorizace pomocí anotace přímo u servletu.

@ServletSecurity(
    @HttpConstraint(rolesAllowed = {"user"}, transportGuarantee = TransportGuarantee.CONFIDENTIAL))

Touto anotací povolíme přístup k servletu pouze uživatelům s rolí user, při spojení bude navíc vynuceno šifrování dat pomocí SSL.

Autentizace

Tomcat podstatně ulehčuje starosti kolem autentizace uživatelů, po minimální konfiguraci totiž sám kontroluje zda se daný uživatel v databázi nachází a zda jsou údaje správné. Je zde možné použít čtyři metody přihlašování: HTTP BASIC, HTTP DIGEST, HTTPS CLIENT a vlastní formuláře.

Konfigurace se provádí v souboru web.xml a vypadá následovně:

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

Touto konfigurací nastavíme jako způsob přihlašování BASIC (podobně je to i s DIGEST a CLIENT), při konfiguraci vlastního formuláře je kód o trochu delší:

<login-config>
    <auth-method>FORM</auth-method>
    <realm-name>Realm</realm-name>
    <form-login-config>
        <form-login-page>/prihlaseni</form-login-page>
        <form-error-page>/chybaprihlaseni</form-error-page>
    </form-login-config>
</login-config>

Zde je nutné navíc nastavit stránku s přihlašovacím formulářem a s hlášením o neúspěchu.

Úprava aplikace

Přejděme už k úpravě samotné aplikace. Uživatele budeme ukládat do již vytvořené databáze, hesla budou zahashována algoritmem MD5, autentizace bude probíhat pomocí HTML formuláře a každý uživatel bude mít přístup pouze ke svým zápiskům.

Databáze

Začněme s úpravou databáze, přidáme do ní dvě nové tabulky – users a user_roles.

Otevřete tedy v NetBeans spojení s naší databázi (vytvořenou v minulém díle) a odešlete první příkaz pro vytvoření tabulky users (username bude primární klíč, password bude řetězec o délce 32, což je délka hashe MD5):

create table users (
  username varchar(15) not null primary key,
  password varchar(32) not null
);

Druhým příkazem vytvořte tabulku user_roles (pár username-rolename bude sloužit jako primární klíč):

create table user_roles (
  username varchar(15) not null,
  rolename varchar(15) not null,
  primary key (username, rolename)
);

Rovnou si vytvoříme i nového uživatele. Klikněte na tabulku users a zvolte View Data, do zobrazené tabulky přidejte nový řádek. Jako username zadejte např. „uzivatel“. Heslo si přeložte do MD5 (například zde: http://www.adamek.biz/md5-generator.php, „heslo“ → 955db0b81ef1989b4a4dfeae8061a9a6) a vložte do sloupce password, přidání nového řádku potvrďte. Nyní klikněte na tabulku user_roles, zvolte View Data a vložte nový řádek. Username zadejte „uzivatel“ a role „user“. Teď je nový uživatel připraven k použití.

Poslední změnou na databázi bude přidání nového sloupce autor do tabulky zapisky. Klikněte tedy pravým tlačítkem na tuto tabulku a zvolte Add Column, jméno (name) sloupce nastavte na autor, type varchar a size 15, nakonec odoznačte možnost null a potvrďte formulář.

Realm

Databázi máme upravenou a můžeme se dát do konfigurace Realmu, otevřete tedy context.xml aplikace a vložte do těla tagu Context tento kód:

<Realm className="org.apache.catalina.realm.DataSourceRealm"
    dataSourceName="jdbc/mysql" digest="MD5" localDataSource="true"
    userTable="users" userNameCol="username" userCredCol="password"
    userRoleTable="user_roles" roleNameCol="rolename"/>

Tím nastavíme jako zdroj Realm-u minule vytvořený Resource, hashování hesel na MD5, tabulka uživatelů má název users, sloupec se jmény username, spoupec s hesly password, tabulka rolí user_roles a sloupec s rolemi rolename.

UpravaZapisku.java

Nyný můžeme upravit třídu UpravaZapisku. Abychom zajistili, že bude každý uživatel moci přistupovat pouze ke svým záznamům, přidáme ke všem dotazům určení autora:

...
    public List<Zapisek> getZapisky(String uzivatel) throws SQLException {
        ...
        try {
            String query = "SELECT * FROM zapisky WHERE autor = ?";            
            connection = getConnection();            
            stmt = connection.prepareStatement(query);        
            stmt.setString(1, uzivatel);
            rs = stmt.executeQuery();
            
        ...
    }
    
    public Zapisek getZapisek(int id, String uzivatel) throws SQLException {
        ...
        try {
            String query = "SELECT * FROM zapisky WHERE id = ? AND autor = ?";            
            connection = getConnection();            
            stmt = connection.prepareStatement(query); 
            stmt.setInt(1, id);
            stmt.setString(2, uzivatel);
            rs = stmt.executeQuery();
            
        ...
    }
    
    public void setZapisek(int id, String nadpis, String obsah, String uzivatel) throws SQLException {
        ...       
        try {
            String query = "UPDATE zapisky SET nadpis = ?, obsah = ? WHERE id = ? AND autor = ?";            
            connection = getConnection();            
            stmt = connection.prepareStatement(query); 
            stmt.setString(1, nadpis);
            stmt.setString(2, obsah);
            stmt.setInt(3, id);
            stmt.setString(4, uzivatel);
            stmt.executeUpdate();
           
        ...
    }
    
    public void addZapisek(String nadpis, String obsah, String uzivatel) throws SQLException {
        ...
        try {
            String query = "INSERT INTO zapisky (nadpis, obsah, autor) VALUES (?, ?, ?)";            
            connection = getConnection();            
            stmt = connection.prepareStatement(query); 
            stmt.setString(1, nadpis);
            stmt.setString(2, obsah);
            stmt.setString(3, uzivatel);
            stmt.executeUpdate();
            
        ...
    }
    
    public void removeZapisek(int id, String uzivatel) throws SQLException {
        ...
        try {
            String query = "DELETE FROM zapisky WHERE id = ? AND autor = ?";            
            connection = getConnection();            
            stmt = connection.prepareStatement(query); 
            stmt.setInt(1, id);
            stmt.setString(2, uzivatel);
            stmt.executeUpdate();
            
        ...
    }  
}

Autorizace

Přejděme ke konfiguraci autorizace a úpravě servletu. Protože máme pouze jediný servlet, nepoužijeme web.xml, ale anotaci. Přidejte tedy tento kód (povolující přístup pouze roli user) nad servlet Controller:

@ServletSecurity(
    @HttpConstraint(rolesAllowed = {"user"}))

Abychom rozeznali jednotlivé uživatele přistupující k servletu, přidejte do metod doPost a doGet tento kód:

String uzivatel = request.getRemoteUser();

Nakonec upravte všechna volání databáze tak, aby jako poslední parametr předávaly jméno přihlášeného uživatele:

...
List<Zapisek> zapisky = databaze.getZapisky(uzivatel);
...
Zapisek zapisek = databaze.getZapisek(id, uzivatel);
...
databaze.addZapisek(nadpis, obsah, uzivatel);
...
databaze.setZapisek(id, nadpis, obsah, uzivatel);
...
databaze.removeZapisek(id, uzivatel); 
...

Autentizace

Už máme nastavené úložiště uživatelů i autorizace, nyní zbývá už jen nastavit autentizaci. Začneme vytvořením vlastního přihlašovacího formuláře. Abychom nemuseli vytvářet druhý (veřejný) controller, vytvoříme soubor prihlasit.jsp ve veřejné složce Web Pages a vložíme do něj tento kód:

<%@page contentType="text/html" pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib tagdir="/WEB-INF/tags" prefix="m" %>

<m:Base titulek="Zápisky">
    <h1>Zápisník - přihlášení</h1>
    
    <form method="POST" action="j_security_check" >
        <input type="hidden" name="id" value="${zapisek.id}" />
        <label for="jmeno">Přihlašovací jméno</label><br>
        <input type="text" name="j_username" id="jmeno" />
        <br>
        <label for="heslo">Heslo</label><br>
        <input type="password" name="j_password" id="heslo" />
        <br>
        <input value="Přihlásit" type="submit" />
    </form>

    <c:if test="${param.upozorneni}">
        <span>Zadané údaje nejsou platné.</span>
    </c:if>
</m:Base>

Abychom vytvořili přihlašovací formulář spravovaný Tomcatem, je potřeba jen velice málo: Formulář musí mít za atribut action dosazený parametr j_security_check, vstup pro jméno musí být označen j_username a heslo j_password. Navíc je zde upozornění na špatně vyplněné údaje.

Zbývá už jen konfigurovat přihlašování v souboru web.xml. Otevřete tedy tento soubor, přesuňte se do karty Security a rozbalte Login Configuration. Nyní označte možnost Form, login page nastavte na /prihlasit.jsp a error page na /prihlasit.jsp?upozorneni=true, Realm name zadejte libovolné.

Závěr

To je vše, nyní můžete aplikaci spustit, přihlásit se a vkládat nové zápisky. V příštím díle se budeme věnovat neoddělitelné části vývoje (a provozu) aplikací – logování, testování a debuggování.

Zdrojové kódy aplikace naleznete na GitHubu: https://github.com/PetrHoracek/JavaNaWeb

Online verze článku: http://www.linuxsoft.cz/article.php?id_article=1987