Autentizace pomocí filtrů – filtry pro autentizaci

2. prosince 2004

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.

Předchozí článek dido.cz
Další článek Naučte se vidět kreativně
Štítky: Články

Mohlo by vás také zajímat

Nejnovější

Napsat komentář

Vaše e-mailová adresa nebude zveřejněna. Vyžadované informace jsou označeny *