[.NET 基於角色安全性驗證] 之二:ASP.NET Forms 身份驗證流程分析
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);
}
}
}
{
// 綁定 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 配置,相關方法用來操作身份驗證數據。
最後更新:2017-04-02 00:06:27