Autentizace pomocí filtrů – návrhový vzor proxy

4. listopadu 2004

V tomto článku si vysvětlíme podstatu návrhového vzoru proxy. Pomocí návrhového vzoru proxy vytvoříme „zástupce“ originálního HTTP požadavku, který bude na některé metody reagovat podle našich potřeb. Pro náš autentizační mechanizmus vytvoříme třídu, jejíž instance bude u cílového servletu zastupovat originální instanci reprezentující HTTP požadavek.

Request

Máme tedy vytvořenou třídu MyPrincipal. Pokud se uživatel přihlásí, vytvoříme její instanci. Musíme ale zajistit, aby instance typu HttpServletRequest, kterou bude mít k dispozici servlet na zavolání metody getUserPrincipal, vracela právě naši instanci. Obdobným způsobem musíme upravit metody getAuthType (bude vracet textový řetězec identifikující způsob autentizace), getRemoteUser (vrátí uživatelské jméno přihlášeného uživatele) a isUserInRole (vrátí „true“ v případě, že uživatel je asociován se zadanou rolí). Ostatní metody zůstanou původní.

Existuje strukturální návrhový vzor proxy. Tento návrhový vzor proxy vkládá mezi objekt, který nabízí nějakou službu, a objekt, který ji požaduje, nějaký objekt (zástupce, náhradník – proxy), který slouží jako mezivrstva. Návrhový vzor proxy má široké uplatnění, my jej využijeme také.

Třídní diagram návrhové vzoru proxy
Třídní diagram návrhové vzoru proxy (Zdroj: A Proxy Design Pattern)

„Klient“ pracuje s třídou Subject. Subject může být jednak přímo instance požadované třídy (RealSubject), nebo zástupce Proxy, který „přeposílá“ volání metod. V obrázku je jako příklad uvedena metoda Request.

Zástupce typu HttpServletRequest

Vytvoříme třídu (zástupce), kterou pojmenujeme MyHttpRequest. Třída bude implementovat rozhraní HttpServletRequest (její instanci bude tedy možné použít kdekoli, kde je očekáván HTTP požadavek). Bude mít jeden atribut typu HttpServletRequest, který bude inicializován v konstruktoru. Naše třída bude vlastně „obalovat“ nějakou instanci typu HttpServletRequest. Metody, které chceme modifikovat pro naši potřebu, napíšeme dle vlastních představ. Ostatní metody jednoduše přepošleme na instanci, kterou jsme obalili.

Znamená to, že budeme mít originální objekt HTTP požadavku a vytvoříme instanci naší třídy, která jej „obalí“. Nový objekt (obal) bude stejného typu jako „zabalený“ objekt. Dále se již bude používat jen „balící“ objekt. Vytvořili jsme vlastně zástupce (proxy) pro HTTP požadavek, je jím právě náš nový obal. Metody, které nás nezajímají a u kterých chceme ponechat původní funkčnost, jednoduše přepošleme na originální objekt. Ostatní metody přepíšeme novou implementací. Zástupce se díky pozměněným metodám bude tvářit jako autentizovaný požadavek. Třída zástupce by mohla vypadat asi takto:

class Proxy implements HTTPServletRequest
{
private HTTPServletRequest req;
public Proxy(HTTPServletRequest r)
{
  super();
  req = r;
}
public void metodaKteraSeNemeni(typ parametr)
{
  return req.metodaKteraSeNemeni(parametr);
}
public String metodaKteraSeMeni(typ parametr)
{
  return „něco jiného“;
}
}

Je zbytečné třídu zástupce vytvářet samostatně. V servlet API (stejně jako v standardních knihovnách Javy) existuje mnoho tříd, které mají ve svém názvu slovo Wrapper (obal). Většinou se jedná o třídy, jejichž instance slouží právě jako zástupci pro komunikaci s originálním objektem. Tyto třídy jsou implementovány tak, že ve všech svých metodách volají metody objektu, který zastupují. Tedy vše je jako metodaKteraSeNemeni v ilustračním příkladu. Znamená to, že samy o sobě nemají žádný smysl. My z nich ale můžeme dědit a metody, které chceme modifikovat, změnit. Metody, které jen přeposílají volání, jsou zděděné a nemusíme je psát. Tím si ušetříme práci.

Existuje jeden takový obal pro instance typu HttpServletRequest, jmenuje se HttpServletRequestWrapper. Vytvoříme potomka této třídy a modifikujeme všechny metody, které je potřeba změnit pro naše účely. Jedná se o metody getAuthType, getRemoteUser, getUserPrincipal a isUserInRole. Ostatní metody budou zděděny a v jejich těle bude zavolána vždy metoda objektu, který bude zastupován instancemi naší třídy. Třídní diagram:

Návrhový vzor proxy aplikovaný na HTTP požadavky
Návrhový vzor proxy aplikovaný na HTTP požadavky

Třída org.apache.coyote.tomcat5.CoyoteRequestFacade je třídou všech HTTP požadavků v TomCatu. Její název není nijak podstatný a nejspíš se bude měnit verzi od verze. Důležité je, že implementuje rozhraní HttpServletRequest.

Drobný rozdíl oproti třídnímu diagramu návrhového vzoru proxy zde přece jenom je. Zatímco zástupce v prvním diagramu je asociován přímo s třídou, jejíž instance zastupuje, v druhém diagramu je asociován s rozhraním, které všechny třídy implementují. V principu se nic nemění, jen se bude pracovat s jiným typem reference na objekt, který je zastupován. Znamená to, že zástupce může zastupovat nejen instance nějaké třídy, ale všechny instance daného typu. Tedy může zastupovat i sám sebe.

Zdrojový text (příklad ke stažení):

package authentication;
import java.security.Principal;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpSession;
public final class MyHttpRequest extends HttpServletRequestWrapper
{
public MyHttpRequest(HttpServletRequest request)
{
  super(request);
}
public String getRemoteUser()
{
  HttpSession session = getSession(false);
  if (session != null)
  {
    MyPrincipal principal = (MyPrincipal) session.getAttribute(Constants.USER);
    if (principal != null)
    {
      return principal.getName();
    }
  }
  return null;
}
public Principal getUserPrincipal()
{
  HttpSession session = getSession(false);
  if (session != null)
  {
    MyPrincipal principal = (MyPrincipal) session.getAttribute(Constants.USER);
    return principal;
  }
  return null;
}
public boolean isUserInRole(String role)
{
  MyPrincipal principal = (MyPrincipal)getUserPrincipal();
  if (principal != null)
  {
    return principal.isUserInRole(role);
  }
  return false;
}
public java.lang.String getAuthType()
{
  HttpSession session = getSession(false);
  if (session != null)
  {
    return (String) session.getAttribute(Constants.AUTHMETHOD);
  }
  return null;
}
}

Máme tedy vytvořenu třídu HTTP požadavků, které budou k dispozici našim servletům při vykonávání. Tím, že servlety dostanou k dispozici instanci třídy MyHttpRequest, budou moci pracovat s požadavkem stejně, jako by se jednalo o požadavek uživatele autentizovaného běžným způsobem. Navíc použití naší třídy MyPrincipal nám usnadní kontrolu uživatelských rolí.

Další článek hodinky
Š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 *