MSDN 中提及 FormsAuthenticationModule 在 Forms 身份验证中起到了关键作用,那么这背后究竟隐藏了什么?本分将简要分析 Forms 身份验证流程,以便让大家更加清楚地了解并使用它。
FormsAuthenticationModule 是一个 Http Module,Forms 身份验证通过 FormsAuthenticationModule 参与 ASP.NET 页的生命周期。它在网站应用启动时被初始化,并拦截访问请求,我们继续看看它的细节。
通过对 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 配置,相关方法用来操作身份验证数据。
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 配置,相关方法用来操作身份验证数据。