Autentizace pomocí filtrů – filtry pro autentizaci
Tentokrát vytvoříme filtry potřebné pro naši autentizaci uživatelů. Jeden filtr vytvoří obal (zástupce) pro HTTP požadavek a druhý filtr pomocí session ověří, zda je uživatel přihlášen a zda je asociován s uživatelskou rolí, která má přístup k požadovanému zdroji. Ukážeme si také, jak se budou naše autentizační filtry konfigurovat.
Filtr obalující požadavek
V článku o návrhovém vzoru proxy jsme si vytvořili třídu MyHttpRequest
. Instance třídy je zástupcem (obalem) instance příchozího HTTP požadavku. Příchozí HTTP požadavek musí být nejprve takto nahrazen zástupcem (musí být obalen). Tento filtr je velmi jednoduchý. Uvedu jen metodu doFilter
:
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException
{
if (request instanceof HttpServletRequest)
{
chain.doFilter((ServletRequest) new MyHttpRequest((HttpServletRequest) request), response);
}
}
Nejprve zjistíme, jestli je příchozí požadavek typu HttpServletRequest (v podstatě formalita). Jestli ano, obalíme příchozí požadavek do instance třídy MyHttpRequest
a předáme jej dalšímu filtru. V opačném případě požadavek k servletu vůbec nedorazí.
Filtr pro ověření uživatele
Druhým filtrem, který si popíšeme, je filtr pro ověření uživatele. Filtr už bude trochu složitější. V konfiguračním souboru web.xml budeme muset nadefinovat uživatelské role, které bude filtr akceptovat (požadavek propustí dále). V metodě init
nejprve zjistíme názvy rolí, které může filtr propustit. Zapíšeme je do kontejneru roles
, které bude typu Set
(množina). Bude se jednat o instanci třídy HashSet
, která rozhraní Set
implementuje. Název každého parametru zadávaného v souboru web.xml pro náš filtr bude začínat řetězcem „role“ (a bude následován třeba číslem). Hodnota parametru bude název role, kterou filtr bude propouštět. Role, které filtr propustí, budou uloženy právě v množině „roles“.
public void init(FilterConfig filterConfig) throws ServletException
{
roles = new HashSet();
for(Enumeration e = config.getInitParameterNames(); e.hasMoreElements();)
{
String roleName = (String) e.nextElement(), role;
if (roleName.startsWith(„role“))
{
role = config.getInitParameter(roleName);
roles.add(role);
}
}
}
V metodě projdu postupně všechny parametry filtru. Pokud název parametru začíná řetězcem „role“, zapíšu jeho hodnotu do kontejneru roles.
Metoda doFilter
ověří, jestli uživatel je asociován s rolí, kterou může filtr propouštět (je v množině rolí „roles“). V metodě provedeme:
- Ověříme, jestli je nastavena množina rolí. Jestli ne, došlo k nějaké podivné chybě a vrátíme odpověď s HTTP kódem 500 – odpovídá hodnotě proměnné
HttpServletResponse.SC_INTERNAL_SERVER_ERROR
. Nemělo by se to nikdy stát. Testování je v podstatě formalita. - Získáme ze session principal přihlášeného uživatele.
- Je-li principal
null
, uživatel není přihlášen. V takovém případě si jako atribut session poznačíme URL cílového dokumentu, který uživatel v požadavku požadoval (budeme se na něj totiž po úspěšném přihlášení vracet) a přesměrujeme uživatele na stránku s přihlašovacím formulářem. - Jestliže principal není
null
, uživatel je přihlášen. V tom případě ověříme, jestli má uživatel asociovánu alespoň jednu roli, kterou může filtr propustit. Jestliže ano, filtr předává požadavek dále. Jestliže ne, odešleme HTTP odpověď s chybovým kódem 403 – odpovídá hodnotě proměnnéHttpServletResponse.SC_FORBIDDEN
(tak reaguje TomCat při standardní autentizaci).
Možná teď někoho napadne, že by bylo lepší místo odeslání HTTP odpovědi s kódem 403 v případě, že uživatel nemá přístup k cílovému dokumentu, poslat uživateli raději nějakou uživatelsky přívětivou stránku. My ale chceme, aby se náš způsob autentizace navenek nijak nelišil od standardní autentizace TomCatu. Chceme-li nějak upravit chybové stránky, můžeme použít (i při naší autentizaci, i při standardní autentizaci) možnosti nakonfigurovat stránky pro chybové stavy pomocí elementu error-page
ve web.xml.
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException
{
String role;
if (roles == null)
{ // Filtr je nějak špatně inicializován
((HttpServletResponse) response).sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
return;
}
HttpSession session = ((HttpServletRequest) request).getSession();
ServletContext application = session.getServletContext();
MyPrincipal principal = (MyPrincipal) session.getAttribute(Constants.USER);
if (principal != null)
{
for(Iterator e = principal.getRoles().iterator(); e.hasNext();)
{
role = (String) e.next();
if (roles.contains(role))
{
chain.doFilter(request, response);
return;
}
}
((HttpServletResponse) response).sendError(HttpServletResponse.SC_FORBIDDEN);
return;
}
/* Zapíšu si URL, na které se uživatel chce přesunout */
session.setAttribute(Constants.REDIRECT, ((HttpServletRequest)request).getRequestURL());
/* Přesměruji klienta na přihlašovací formulář*/
((HttpServletResponse) response).sendRedirect((String)application.getAttribute(authentication.Constants.LOGINFORM));
}
Konfigurace
Filtry budeme konfigurovat v souboru web.xml. Nejprve musíme zajistit, aby všechny požadavky přicházející do naší aplikace byly „obaleny“ instancí naší třídy MyHttpRequest
. Proto vytvoříme filtr třídy RequestWrappingFilter
a pojmenujeme jej třeba RequestWrappingFilter
.
<filter>
<filter-name>RequestWrappingFilter</filter-name>
<filter-class>authentication.RequestWrappingFilter</filter-class>
</filter>
Tímto filtrem musí projít všechny požadavky na servlety naší aplikace. Namapujeme jej proto pomocí masky /*
:
<filter-mapping>
<filter-name>RequestWrappingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
A nyní pomocí druhého filtru nastavíme chráněné zóny. Například si představme, že vše, čemu odpovídá maska /zakaznik/*
, má být přístupno jen pro roli zakaznik
. Vše, co odpovídá masce /navstevnik/*
, má být přístupno roli navstevnik
. A vše, čemu odpovídá maska /oboji/*
, má být přístupno oběma rolím (tedy stačí, aby uživatel byl alespoň v jedné roli). Tento příklad bychom ve web.xml nakonfigurovali takto:
<filter>
<filter-name>ClientFilter</filter-name>
<filter-class>authentication.AuthenticationFilter</filter-class>
<init-param>
<param-name>role1</param-name>
<param-value>zakaznik</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>ClientFilter</filter-name>
<url-pattern>/zakaznik/*</url-pattern>
</filter-mapping>
Filtr se jmenuje ClientFilter
a je namapován na masku /zakaznik/*
. Obdobně definujeme filtr pro návštěvníka.
<filter>
<filter-name>VisitorFilter</filter-name>
<filter-class>authentication.AuthenticationFilter</filter-class>
<init-param>
<param-name>role1</param-name>
<param-value>navstevnik</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>VisitorFilter</filter-name>
<url-pattern>/navstevnik/*</url-pattern>
</filter-mapping>
Třetí filtr propustí požadavky uživatelů, kteří jsou buď zákazníci, nebo návštěvníci.
<filter>
<filter-name>VisitorClientFilter</filter-name>
<filter-class>authentication.AuthenticationFilter</filter-class>
<init-param>
<param-name>role1</param-name>
<param-value>navstevnik</param-value>
</init-param>
<init-param>
<param-name>role2</param-name>
<param-value>zakaznik</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>VisitorClientFilter</filter-name>
<url-pattern>/oboji/*</url-pattern>
</filter-mapping>
Kdybychom chtěli pro nějakou zónu (specifikovanou maskou) propustit jen uživatele, kteří jsou v roli zákazníka a zároveň v roli návštěvníka (v tomto konkrétním případě asi příliš nedává smysl, ale jiných situacích se může hodit), namapujeme na danou masku postupně filtry ClientFiltr
a VisitorFiltr
.
V článku jsme vytvořili filtry, které zajistí autentizaci uživatele (zdroj). Filtry berou údaje o uživateli ze session. V dalších článcích vytvoříme něco, co právě tyto informace o uživateli do session při přihlášení nebo při registraci (a tím pádem i přihlášení) vytvoří.
Starší komentáře ke článku
Pokud máte zájem o starší komentáře k tomuto článku, naleznete je zde.
Mohlo by vás také zajímat
-
Jak zabezpečit váš chytrý telefon před kybernetickými hrozbami
30. listopadu 2023
Nejnovější
-
Jak rozšířit úložiště Macu za pětinovou cenu?
16. prosince 2024 -
Nové trendy v doménách pro osobní projekty – DIY, LIVING a LIFESTYLE
9. prosince 2024 -
Jak chránit webové stránky před Web/AI Scrapingem
27. listopadu 2024 -
Jaký monitor je nejlepší k novému Macu Mini?
25. listopadu 2024