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 配置,相关方法用来操作身份验证数据。