[.NET 基於角色安全性驗證] 之二:ASP.NET Forms 身份驗證流程分析

NO IMAGE

MSDN 中提及 FormsAuthenticationModule 在 Forms 身份驗證中起到了關鍵作用,那麼這背後究竟隱藏了什麼?本分將簡要分析 Forms 身份驗證流程,以便讓大家更加清楚地瞭解並使用它。

FormsAuthenticationModule 是一個 Http Module,Forms 身份驗證通過 FormsAuthenticationModule 參與 ASP.NET 頁的生命週期。它在網站應用啟動時被初始化,並攔截訪問請求,我們繼續看看它的細節。

public void Init(HttpApplication app)
{
  // 繫結 HttpApplication 的兩個事件,以此來攔截系統的訪問請求。
  app.AuthenticateRequest = new EventHandler(this.OnEnter);
  app.EndRequest = new EventHandler(this.OnLeave);
}

private void OnEnter(object source, EventArgs eventArgs)
{
  if (!FormsAuthenticationModule._fAuthChecked || FormsAuthenticationModule._fAuthRequired)
  {
    HttpApplication application1 = (HttpApplication)source;
    HttpContext context1 = application1.Context;

    // 讀取 Forms 認證的配置資訊
    AuthenticationSection section1 = RuntimeConfig.GetAppConfig().Authentication;
    section1.ValidateAuthenticationMode();

    // 確認 Forms 驗證模式
    if (!FormsAuthenticationModule._fAuthChecked)
    {
      FormsAuthenticationModule._fAuthRequired = section1.Mode == AuthenticationMode.Forms;
      FormsAuthenticationModule._fAuthChecked = true;
    }

    if (FormsAuthenticationModule._fAuthRequired)
    {
      // 設定預設引數
      if (!this._fFormsInit)
      {
        FormsAuthentication.Initialize();
        this._FormsName = section1.Forms.Name;
        if (this._FormsName == null)
        {
          this._FormsName = “.ASPXAUTH”;
        }
        FormsAuthenticationModule.Trace(“Forms name is: ” this._FormsName);
        this._LoginUrl = section1.Forms.LoginUrl;
        if (this._LoginUrl == null)
        {
          this._LoginUrl = “login.aspx”;
        }
        this._fFormsInit = true;
      }

      // 呼叫驗證核心方法
      this.OnAuthenticate(new FormsAuthenticationEventArgs(context1));

      CookielessHelperClass class1 = context1.CookielessHelper;
      if (AuthenticationConfig.AccessingLoginPage(context1, this._LoginUrl))
      {
        context1._skipAuthorization = true;
        class1.RedirectWithDetectionIfRequired(null, FormsAuthentication.CookieMode);
      }
      if (!context1.SkipAuthorization)
      {
        context1._skipAuthorization = AssemblyResourceLoader.IsValidWebResourceRequest(context1);
      }
    }
  }
}

private void OnAuthenticate(FormsAuthenticationEventArgs e)
{
  HttpCookie cookie1 = null;
  if (this._eventHandler != null)
  {
    this._eventHandler(this, e);
  }

  // 檢查 User 物件,以確認是否已通過驗證。
  if ((e.Context.User != null) || (e.User != null))
  {
    // 處理 Global.asax FormsAuthentication_OnAuthenticate 事件引數。
    if (e.Context.User == null)
    {
      e.Context._user = e.User;
    }
  }
  else
  {
    FormsAuthenticationTicket ticket1 = null;
    bool flag1 = false;
    try
    {
      // 從 Cookie 中提取驗證票證物件
      ticket1 = FormsAuthenticationModule.ExtractTicketFromCookie(e.Context, this._FormsName, out flag1);
    }
    catch
    {
      ticket1 = null;
    }

    if ((ticket1 != null) && !ticket1.Expired)
    {
      FormsAuthenticationTicket ticket2 = ticket1;

      // 重新整理票證資訊
      if (FormsAuthentication.SlidingExpiration)
      {
        ticket2 = FormsAuthentication.RenewTicketIfOld(ticket1);
      }

      // 建立主體和標識物件
      e.Context._user = new GenericPrincipal(new FormsIdentity(ticket2), new string[0]);

      if (!flag1 && !ticket2.CookiePath.Equals(“/”))
      {
        cookie1 = e.Context.Request.Cookies[this._FormsName];
        if (cookie1 != null)
        {
          cookie1.Path = ticket2.CookiePath;
        }
      }
      if (ticket2 != ticket1)
      {
        if ((flag1 && (ticket2.CookiePath != “/”)) && (ticket2.CookiePath.Length > 1))
        {
          ticket2 = new FormsAuthenticationTicket(ticket2.Version, ticket2.Name, ticket2.IssueDate, ticket2.Expiration, ticket2.IsPersistent, ticket2.UserData, “/”);
        }

        // 加密新的票證,並寫入 Cookie。
        string text1 = FormsAuthentication.Encrypt(ticket2);
        if (flag1)
        {
          e.Context.CookielessHelper.SetCookieValue(‘F’, text1);
          e.Context.Response.Redirect(e.Context.Request.PathWithQueryString);
        }
        else
        {
          if (cookie1 != null)
          {
            cookie1 = e.Context.Request.Cookies[this._FormsName];
          }
          if (cookie1 == null)
          {
            cookie1 = new HttpCookie(this._FormsName, text1);
            cookie1.Path = ticket2.CookiePath;
          }
          if (ticket2.IsPersistent)
          {
            cookie1.Expires = ticket2.Expiration;
          }
          cookie1.Value = text1;
          cookie1.Secure = FormsAuthentication.RequireSSL;
          cookie1.HttpOnly = true;
          if (FormsAuthentication.CookieDomain != null)
          {
            cookie1.Domain = FormsAuthentication.CookieDomain;
          }
          e.Context.Response.Cookies.Add(cookie1);
        }
      }
    }
  }
}

private void OnLeave(object source, EventArgs eventArgs)
{
  // 調整 URL
  if (FormsAuthenticationModule._fAuthChecked && FormsAuthenticationModule._fAuthRequired)
  {
    HttpApplication application1 = (HttpApplication)source;
    HttpContext context1 = application1.Context;
    if (context1.Response.StatusCode == 0x191)
    {
      string text3;
      string text1 = null;
      if (!string.IsNullOrEmpty(this._LoginUrl))
      {
        text1 = AuthenticationConfig.GetCompleteLoginUrl(context1, this._LoginUrl);
      }
      if ((text1 == null) || (text1.Length <= 0))
      {
        throw new HttpException(SR.GetString(“Auth_Invalid_Login_Url”));
      }
      CookielessHelperClass class1 = context1.CookielessHelper;
      string text2 = context1.Request.PathWithQueryString;
      if (text1.IndexOf(‘?’) >= 0)
      {
        text1 = FormsAuthentication.RemoveQueryStringVariableFromUrl(text1, “ReturnUrl”);
        text3 = text1 “&ReturnUrl=” HttpUtility.UrlEncode(text2, context1.Request.ContentEncoding);
      }
      else
      {
        text3 = text1 “?ReturnUrl=” HttpUtility.UrlEncode(text2, context1.Request.ContentEncoding);
      }
      int num1 = text2.IndexOf(‘?’);
      if ((num1 >= 0) && (num1 < (text2.Length – 1)))
      {
        text2 = FormsAuthentication.RemoveQueryStringVariableFromUrl(text2, “ReturnUrl”);
      }
      num1 = text2.IndexOf(‘?’);
      if ((num1 >= 0) && (num1 < (text2.Length – 1)))
      {
        text3 = text3 “&” text2.Substring(num1 1);
      }
      class1.SetCookieValue(‘F’, null);
      class1.RedirectWithDetectionIfRequired(text3, FormsAuthentication.CookieMode);
      context1.Response.Redirect(text3, false);
    }
  }
}

通過對 FormsAuthenticationModule 程式碼的分析,我們基本可以確定 ASP.NET Forms 身份驗證流程。

1. 攔截系統訪問,檢查身份驗證狀態。
2. 如未通過驗證,則跳轉到指定的 loginUrl 接受使用者身份資料驗證。
3. 如已通過驗證,則從 Cookie 中提取身份驗證票證物件。
4. 建立使用者標識和主體物件,其中標識物件包含了票證引用。
5. 更新票證過期時間,重新寫入 Cookie。
6. 調整URL引數,重定向頁面。

在整個驗證流程中,我們可以看到幾個驗證的核心型別。包括:

FormsAuthenticationTicket :身份驗證票證,儲存使用者名稱、過期時間、自定義資料等資訊。經 FormsAuthentication.Encrypt 方法加密成字串後儲存到 Cookie 或者 URL 引數中。

GenericPrincipal :使用者主體物件。HttpContext.User 就是該型別,用來儲存使用者身份資料,諸如:FormsIdentity、FormsAuthenticationTicket 等。

FormsIdentity:使用者標識物件,可通過 HttpContext.Current.User.Identity 訪問。其 Ticket 屬性儲存了使用者身份驗證票據引用。

HttpContext:ASP.NET 為每個使用者建立的上下文物件,用來儲存該使用者的相關資訊,諸如 Session、GenericPrincipal 等。

FormsAuthentication:ASP.NET Forms 身份驗證的核心類之一,其靜態屬性可訪問 Forms Web.config 配置,相關方法用來操作身份驗證資料。