Forms autentizace

Tomáš Holan       04.05.2012       ASP.NET/IIS, Bezpečnost       12504 zobrazení

Jeden z nejčastěji používaných způsobu řešení autentizace v ASP.NET aplikacích je použití Forms autentizace (Forms Authentication) v kombinaci s membership providerem.

Membership providerů existuje celá řada od výchozích, které jsou součástí .NET Frameworku (například SqlMembershipProvider), přes mnoho veřejně dostupných (například ASP.NET Universal Providers, nebo Altairis Web Security Toolkit) nebo třeba máme vyvinut a používáme nějaký vlastní.

To co v případě jakéhokoliv membership providera zůstává přitom stále stejné je právě mechanizmus vlastní forms autentizace tj. ověřování jednotlivých klientských requestů na základě vystaveného Forms autentizačního tiketu ukládaného v cookie (*). Tento proces si rozebereme trochu podrobněji.

Přihlášení uživatele

Uživatel vstoupí do webové aplikace na výchozí stránku, která buď rovnou slouží jako login page, nebo je na login page přesměrován. V druhém případě se tak děje, protože uživatel není ještě přihlášen a pro stránku default.aspx (a další stránky) máme ve web.config zakázán přístup anonymního tj. neautentizovaného uživatele.

<location path="Default.aspx">
  <system.web>
    <authorization>
      <deny users="?"/>
    </authorization>
  </system.web>
</location>

Na stránce pro přihlášení zadá uživatel přihlašovací údaje a po odeslání přihlašovacího formuláře (postback) provede ASP.NET Login control (nebo vlastní kód obsluhující přihlášení) volání ověření uživatele pomoci membership providera.

bool authenticated = Membership.Provider.ValidateUser(txtUserName.Text, txtPassword.Text);

Pokud vrací toto volání hodnotu true, je volána statická metoda SetAuthCookie třídy FormsAuthentication a následně se provede redirect zpět na původní, nastavenou nebo výchozí stránku (dle nastavení Login controlu). Jako druhý parametr volání metody SetAuthCookie je předáno, zda má být cookie uloženo perzistentně tj. hodnota volby “Zůstat přihlášen(a)” z přihlašovacího formuláře.

if (authenticated)
{
    FormsAuthentication.SetAuthCookie(txtUserName.Text, chckRememberMe.Checked);
    this.Page.Response.Redirect(this.GetRedirectUrl());
}

Uvnitř volání SetAuthCookie je vytvořeno cookie, které obsahuje forms autentizační tiket. Cookie je pak vloženo do aktuální response.

public static void SetAuthCookie(string userName, bool createPersistentCookie)
{
    HttpCookie cookie = GetAuthCookie(userName, createPersistentCookie, FormsAuthentication.FormsCookiePath);
    HttpContext.Current.Response.Cookies.Add(cookie);
}

private static HttpCookie GetAuthCookie(string userName, bool createPersistentCookie, string strCookiePath)
{
    if (userName == null)
    {
        userName = "";
    }
    if (string.IsNullOrEmpty(strCookiePath))
    {
        strCookiePath = FormsAuthentication.FormsCookiePath;
    }

    var ticket = new FormsAuthenticationTicket(
        2,
        userName,
        DateTime.Now,
        DateTime.Now.AddMinutes(this.Timeout),
        createPersistentCookie,
        "" /*userData*/,
        strCookiePath);

    string cookieValue = FormsAuthentication.Encrypt(ticket);
    HttpCookie cookie = new HttpCookie(FormsAuthentication.FormsCookieName, cookieValue)
    {
        HttpOnly = true,
        Path = strCookiePath,
        Secure = FormsAuthentication.RequireSSL
    };
    if (FormsAuthentication.CookieDomain != null)
    {
        cookie.Domain = FormsAuthentication.CookieDomain;
    }
    if (ticket.IsPersistent)
    {
        cookie.Expires = ticket.Expiration;
    }
    return cookie;
}

Všimněte si, že do autentizačního tiketu lze vložit vlastní user data (vlastnost UserData), metoda SetAuthCookie, ale do tiketu jako tato data vloží vždy prázdný řetězec.

Po provedení redirectu a při dalších následných requestech je na základě existence autentizačního cookie prováděna autentizace requestu.

Autentizace requestu

Pokud byl uživatel výše uvedeným postupem přihlášen (nebo bylo provedeno zapamatování přihlášení – vytvoření perzistentního cookie – a uživatel vstupuje do ASP.NET aplikace), je v requestu přítomno autentizační cookie.

Tím, že zapneme Forms autentizaci uvedením

<authentication mode="Forms" />

do souboru web.config, je do zpracování každého requestu zapojen HTTP modul FormsAuthenticationModule. Jeho akce jsou spouštěny konkrétně ve fázi AuthenticateRequest.

public sealed class FormsAuthenticationModule : IHttpModule
{
    public void Init(HttpApplication app)
    {
        app.AuthenticateRequest += new EventHandler(this.OnAuthenticate);
    }

    //...
}

Hlavními úkoly, tohoto modulu, je jednak provádět obnovování exspirace autentizačního tiketu v případě SlidingExpiration a vystavit do vlastnosti HttpContext.Current.User příslušný principál reprezentující aktuálně přihlášeného uživatele.

private void OnAuthenticate(object source, EventArgs eventArgs)
{
    HttpApplication application = (HttpApplication)source;
    HttpContext context = application.Context;

    var e = new FormsAuthenticationEventArgs(context);
    if (Authenticate != null)
    {
        Authenticate(this, e);
    }

    if (context.User != null)
    {
        return;
    }
    if (e.User != null)
    {
        context.User = e.User;  //Set principal
        return;
    }

    FormsAuthenticationTicket tOld = ExtractTicketFromCookie(context, FormsAuthentication.FormsCookieName);
    if (tOld != null)
    {
        FormsAuthenticationTicket ticket = tOld;
        if (FormsAuthentication.SlidingExpiration)
        {
            ticket = FormsAuthentication.RenewTicketIfOld(tOld);
        }

        context.User = new GenericPrincipal(new FormsIdentity(ticket), new string[0]);  //Set principal

        if (ticket != tOld)
        {
            HttpCookie cookie = context.Request.Cookies[FormsAuthentication.FormsCookieName];
            cookie.Value = FormsAuthentication.Encrypt(ticket);
            cookie.Path = ticket.CookiePath;
            cookie.Secure = FormsAuthentication.RequireSSL;
            cookie.HttpOnly = true;
            if (FormsAuthentication.CookieDomain != null)
            {
                cookie.Domain = FormsAuthentication.CookieDomain;
            }
            if (ticket.IsPersistent)
            {
                cookie.Expires = ticket.Expiration;
            }

            context.Response.Cookies.Remove(cookie.Name);
            context.Response.Cookies.Add(cookie);
        }
    }
}

private static FormsAuthenticationTicket ExtractTicketFromCookie(HttpContext context, string name)
{
    HttpCookie cookie = context.Request.Cookies[name];
    if (cookie != null)
    {
        string encryptedTicket = cookie.Value;
        if (encryptedTicket != null && encryptedTicket.Length > 1)
        {
            FormsAuthenticationTicket ticket = null;
            try
            {
                ticket = FormsAuthentication.Decrypt(encryptedTicket);
            }
            catch
            {
                //Ignore exception
            }
            if (ticket != null && !ticket.Expired)
            {
                return ticket;
            }
            context.Request.Cookies.Remove(name);
        }
    }
    return null;
}

Před vlastním zpracování je nejprve volána událost Authenticate, která nám dává možnost kompletně zaměnit další akce vlastním kódem (kód musí nastavit vlastnost User v předaných FormsAuthenticationEventArgs, nebo případně přímo HttpContext.User aktuálního HTTP kontextu).

Obsluha této události se provádí ve zdrojovém souboru Global.asax aplikace:

public void FormsAuthentication_OnAuthenticate(object sender, FormsAuthenticationEventArgs e)
{
    //e.User = ...
}

Při zpracování requestu je dále metodou ExtractTicketFromCookie z hodnoty cookie dešifrován autentizační tiket. Pokud je tiket stále platný je následně vrácen.

V případě sliding exspirace tiketu je volána metoda FormsAuthentication.RenewTicketIfOld pro jeho případné obnovení. Pokud tak bylo učiněno, je následně provedena i aktualizace autentizačního cookie v aktuálním response.

Poslední akcí je nastavení objektu principál. Principál, který FormsAuthenticationModule nastavuje do aktuálního HTTP kontextu je konkrétně typu GenericPrincipal, má prázdný seznam rolí (**) a Identity typu FormsIdentity obsahující aktuální autentizační tiket.

protected void Page_Load(object sender, EventArgs e)
{
    if (User.Identity.IsAuthenticated)
    {
        string userName = User.Identity.Name;
        //...
    }
}

Odhlášení uživatele

Odhlášení uživatele iniciuje například ASP.NET control LoginStatus, který provede volání metody FormsAuthentication.SignOut a následně redirect na nastavenou, přihlašovací nebo aktuální stránku (to se řídí nastavením vlastnosti LogoutAction controlu LoginStatus).

FormsAuthentication.SignOut();
this.Page.Response.Clear();
this.Page.Response.StatusCode = 200;
//this.Page.Response.Redirect(ResolveClientUrl(this.LogoutPageUrl), false); //LogoutAction.Redirect
//this.Page.Response.Redirect(FormsAuthentication.LoginUrl, false);         //LogoutAction.RedirectToLoginPage
this.Page.Response.Redirect(this.Page.Request.RawUrl, false);               //LogoutAction.Refresh

Metoda FormsAuthentication.SignOut provádí odstranění autentizačního cookie pro aktuální response.

public static void SignOut()
{
    HttpContext current = HttpContext.Current;

    string str = string.Empty;
    if (current.Request.Browser["supportsEmptyStringInCookieValue"] == "false")
    {
        str = "NoCookie";
    }
    HttpCookie cookie = new HttpCookie(FormsAuthentication.FormsCookieName, str);
    cookie.HttpOnly = true;
    cookie.Path = FormsAuthentication.FormsCookiePath;
    cookie.Expires = new DateTime(0x7cf, 10, 12);
    cookie.Secure = FormsAuthentication.RequireSSL;
    if (FormsAuthentication.CookieDomain != null)
    {
        cookie.Domain = FormsAuthentication.CookieDomain;
    }
    current.Response.Cookies.RemoveCookie(FormsAuthentication.FormsCookieName);
    current.Response.Cookies.Add(cookie);
}

Po odstranění cookie a provedení redirectu není v následném (a dalších) requestech cookie přítomno, modul FormsAuthenticationModule neprovede žádnou akci a uživatel zůstává nadále odhlášen.


(*) nebo přímo součástí URL – tento druhý způsob ale není moc používaný.

(**) V případě povolení Role providera pomoci:

<system.web>
    <roleManager enabled="true" />
</system.web>

je navíc za FormsAuthenticationModule zapojen i RoleManagerModule HTTP modul, který (ve fázi PostAuthenticateRequest ) nahradí GenericPrincipal za objekt RolePrincipal. Jeho metoda IsInRole pak umožňuje navíc provádět role-based autorizaci.

protected void Page_Load(object sender, EventArgs e)
{
    if (User.IsInRole("Administrators"))
    {
        //...
    }
}

Více informací k tomuto naleznete zde a zde.

 

hodnocení článku

3 bodů / 4 hlasů       Hodnotit mohou jen registrované uživatelé.

 

Nový příspěvek

 

RE: Forms autentizace

Pěkný přehled, díky

Petr Snobelt

nahlásit spamnahlásit spam 0 odpovědětodpovědět
                       
Nadpis:
Antispam: Komu se občas házejí perly?
Příspěvek bude publikován pod identitou   anonym.

Nyní zakládáte pod článkem nové diskusní vlákno.
Pokud chcete reagovat na jiný příspěvek, klikněte na tlačítko "Odpovědět" u některého diskusního příspěvku.

Nyní odpovídáte na příspěvek pod článkem. Nebo chcete raději založit nové vlákno?

 

  • Administrátoři si vyhrazují právo komentáře upravovat či mazat bez udání důvodu.
    Mazány budou zejména komentáře obsahující vulgarity nebo porušující pravidla publikování.
  • Pokud nejste zaregistrováni, Vaše IP adresa bude zveřejněna. Pokud s tímto nesouhlasíte, příspěvek neodesílejte.

přihlásit pomocí externího účtu

přihlásit pomocí jména a hesla

Uživatel:
Heslo:

zapomenuté heslo

 

založit nový uživatelský účet

zaregistrujte se

 
zavřít

Nahlásit spam

Opravdu chcete tento příspěvek nahlásit pro porušování pravidel fóra?

Nahlásit Zrušit

Chyba

zavřít

feedback