本文將介紹.net中Form身份驗證的必要元素如FormsAuthentication.SetAuthCookie,FormsAuthentication.Encrypt,FormsAuthentication.Decrypt,machineKey,Identity,Principal等和.net如何定義和實現他們。
一.介紹一些我們的應用場景吧
index.aspx要求登陸,如果沒有登陸跳轉到login.aspx頁面,登錄成功后跳轉到index.aspx頁面,並顯示必要信息.
二.實現:
2.1 web.config:
- <system.web>
- <authentication mode="Forms">
- <forms name="adminlogin" loginUrl="login.aspx">
- </forms>
- </authentication>
- <authorization>
- <deny users="?"/>
- </authorization>
- </system.web>
注意:<authorization>
<deny users="?"/>
</authorization> 不是必須的。
如果有的話,當我們訪問index.aspx時,.net底層負責檢測是否登錄,如果沒有登陸,.net根據我們的配置跳轉的login.aspx頁面。
如果沒有的話,必須我們自己寫必要的代碼,來跳轉。
2.2 login.aspx主要進行身份驗證,.net中有下列實現:
2.1.1 FormsAuthentication.RedirectFromLoginPage
- if (username == "admin" && password == "1")
- {
-
FormsAuthentication.RedirectFromLoginPage(username, true); - }
該作用是設置票據,自動跳轉到來源頁面(index.aspx).注意來源頁面必須帶一個ReturnUrl,否則會出錯
- if (!User.Identity.IsAuthenticated)
- {
- Response.Redirect("/login.aspx?ReturnUrl=/index.aspx");
- }
2.2.2 FormsAuthentication.SetAuthCookie
- if (username == "admin" && password == "1")
- {
- FormsAuthentication.SetAuthCookie(username, true);
- Response.Redirect("index.aspx");
- }
該方法比第一種方法有更大的靈活性,可以自定義登陸成功后,返回的url.
2.2.3 自定義票據cookie
- if (username == "admin" && password == "1")
- {
- FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(1, username, DateTime.Now, DateTime.Now.AddMinutes(30), false, "Manager");
- string cookieStr = FormsAuthentication.Encrypt(ticket);
- HttpCookie cookie = new HttpCookie(FormsAuthentication.FormsCookieName, cookieStr);
- cookie.Expires = ticket.Expiration;
- cookie.Path = FormsAuthentication.FormsCookiePath;
- Response.Cookies.Add(cookie);
- Response.Redirect("index.aspx");
- }
該方法比第二中方法又有更大的靈活性。
2.3.1
FormsAuthentication.RedirectFromLoginPage的原理:
- public static void RedirectFromLoginPage(string userName, bool createPersistentCookie, string strCookiePath)
- {
- Initialize();
- if (userName != null)
- {
- HttpContext current = HttpContext.Current;
- string returnUrl = GetReturnUrl(true);
- if (CookiesSupported || IsPathWithinAppRoot(current, returnUrl))
- {
- SetAuthCookie(userName, createPersistentCookie, strCookiePath);
- returnUrl = RemoveQueryStringVariableFromUrl(returnUrl, FormsCookieName);
- if (!CookiesSupported)
- {
- int index = returnUrl.IndexOf("://", StringComparison.Ordinal);
- if (index > 0)
- {
- index = returnUrl.IndexOf('/', index + 3);
- if (index > 0)
- {
- returnUrl = returnUrl.Substring(index);
- }
- }
- }
- }
- else
- {
- if (!EnableCrossAppRedirects)
- {
- throw new HttpException(System.Web.SR.GetString("Can_not_issue_cookie_or_redirect"));
- }
- HttpCookie cookie = GetAuthCookie(userName, createPersistentCookie, strCookiePath);
- returnUrl = RemoveQueryStringVariableFromUrl(returnUrl, cookie.Name);
- if (returnUrl.IndexOf('?') > 0)
- {
- string str2 = returnUrl;
- returnUrl = str2 + "&" + cookie.Name + "=" + cookie.Value;
- }
- else
- {
- string str3 = returnUrl;
- returnUrl = str3 + "?" + cookie.Name + "=" + cookie.Value;
- }
- }
- current.Response.Redirect(returnUrl, false);
- }
- }
- internal static string GetReturnUrl(bool useDefaultIfAbsent)
- {
- Initialize();
- HttpContext current = HttpContext.Current;
- string str = current.Request.QueryString["ReturnUrl"];
- if (str == null)
- {
- str = current.Request.Form["ReturnUrl"];
- if ((!string.IsNullOrEmpty(str) && !str.Contains("/")) && str.Contains("%"))
- {
- str = HttpUtility.UrlDecode(str);
- }
- }
- if ((!string.IsNullOrEmpty(str) && !EnableCrossAppRedirects) && !UrlPath.IsPathOnSameServer(str, current.Request.Url))
- {
- str = null;
- }
- if (!string.IsNullOrEmpty(str) && CrossSiteScriptingValidation.IsDangerousUrl(str))
- {
- throw new HttpException(System.Web.SR.GetString("Invalid_redirect_return_url"));
- }
- if ((str == null) && useDefaultIfAbsent)
- {
- return DefaultUrl;
- }
- return str;
- }
本質上:FormsAuthentication.RedirectFromLoginPage是調用了FormsAuthentication.SetAuthCookie,然后Response.Redirect(current.Request.QueryString["ReturnUrl"],
false)
2.3.2 FormsAuthentication.SetAuthCookie原理:
- public static void SetAuthCookie(string userName, bool createPersistentCookie, string strCookiePath)
- {
- Initialize();
- HttpContext current = HttpContext.Current;
- if (!current.Request.IsSecureConnection && RequireSSL)
- {
- throw new HttpException(System.Web.SR.GetString("Connection_not_secure_creating_secure_cookie"));
- }
- bool flag = CookielessHelperClass.UseCookieless(current, false, CookieMode);
- HttpCookie cookie = GetAuthCookie(userName, createPersistentCookie, flag ? "/" : strCookiePath, !flag);
- if (!flag)
- {
- HttpContext.Current.Response.Cookies.Add(cookie);
- current.CookielessHelper.SetCookieValue('F', null);
- }
- else
- {
- current.CookielessHelper.SetCookieValue('F', cookie.Value);
- }
- }
- private static HttpCookie GetAuthCookie(string userName, bool createPersistentCookie, string strCookiePath, bool hexEncodedTicket)
- {
- Initialize();
- if (userName == null)
- {
- userName = string.Empty;
- }
- if ((strCookiePath == null) || (strCookiePath.Length < 1))
- {
- strCookiePath = FormsCookiePath;
- }
- FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(2, userName, DateTime.Now, DateTime.Now.AddMinutes((double) _Timeout), createPersistentCookie, string.Empty, strCookiePath);
- string str = Encrypt(ticket, hexEncodedTicket);
- if ((str == null) || (str.Length < 1))
- {
- throw new HttpException(System.Web.SR.GetString("Unable_to_encrypt_cookie_ticket"));
- }
- HttpCookie cookie = new HttpCookie(FormsCookieName, str);
- cookie.HttpOnly = true;
- cookie.Path = strCookiePath;
- cookie.Secure = _RequireSSL;
- if (_CookieDomain != null)
- {
- cookie.Domain = _CookieDomain;
- }
- if (ticket.IsPersistent)
- {
- cookie.Expires = ticket.Expiration;
- }
- return cookie;
- }
FormsAuthentication.SetAuthCookie本質上調用了FormsAuthentication.GetAuthCookie,而FormsAuthentication.GetAuthCookie得代碼和我們上面跳到的第三種方法何其相似呀。
注意了:第二種方法和第三種方法還是有區別的。
a.第三種方法可以設置更多的信息如:
第二種:附加信息為string.Empty
FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(2, userName,
DateTime.Now, DateTime.Now.AddMinutes((double) _Timeout),
createPersistentCookie, string.Empty,
strCookiePath);
第二種:附加信息為"Manager"
FormsAuthenticationTicket ticket = new
FormsAuthenticationTicket(1, username, DateTime.Now,
DateTime.Now.AddMinutes(30), false, "Manager");
b.第三種方法可以更靈活的設置domain等信息。
第二種使用的是默認的,如果在web.config配置了domain,就使用config中的
- <authentication mode="Forms">
- <forms name="adminlogin" loginUrl="loginRedirect.aspx" domain ="bbs.it118.org">
- </forms>
- </authentication>
否則使用當前url的domain.
但還是有個問題如果我在web.comfig
domain為bbs.it118.org的情況下,想設置domain為it118.org.第二種就必須如下寫了:
- FormsAuthentication.SetAuthCookie(user.UserNumb, isSave == true);
- string cookieName = FormsAuthentication.FormsCookieName;
- HttpCookie cookie = HttpContext.Current.Response.Cookies[cookieName];
- if (cookie != null)
- {
- cookie.Domain = "it118.org";
- }
2.3.3我們在分析第三種方法的原理
//生成票據,帶有附加信息
FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(1,
username, DateTime.Now, DateTime.Now.AddMinutes(30), false, "Manager");
//加密票據
string cookieStr = FormsAuthentication.Encrypt(ticket);
//生成一個cookie,
//cookiename來自web.config配置的name
- <authentication mode="Forms">
- <forms name="adminlogin" loginUrl="loginRedirect.aspx" domain ="bbs.it118.org">
- </forms>
- </authentication>
//cookie的值來自加密的票據
HttpCookie cookie = new
HttpCookie(FormsAuthentication.FormsCookieName, cookieStr);
//設置cookie的過期時間和路徑等
cookie.Expires =
ticket.Expiration;
cookie.Path =
FormsAuthentication.FormsCookiePath;
Response.Cookies.Add(cookie);
Response.Redirect("index.aspx");
這里重點提一下加密算法
- private static string Encrypt(FormsAuthenticationTicket ticket, bool hexEncodedTicket)
- {
- if (ticket == null)
- {
- throw new ArgumentNullException("ticket");
- }
- Initialize();
- byte[] buf = MakeTicketIntoBinaryBlob(ticket);
- if (buf == null)
- {
- return null;
- }
- if ((_Protection == FormsProtectionEnum.All) || (_Protection == FormsProtectionEnum.Validation))
- {
- byte[] src = MachineKeySection.HashData(buf, null, 0, buf.Length);
- if (src == null)
- {
- return null;
- }
- byte[] dst = new byte[src.Length + buf.Length];
- Buffer.BlockCopy(buf, 0, dst, 0, buf.Length);
- Buffer.BlockCopy(src, 0, dst, buf.Length, src.Length);
- buf = dst;
- }
- if ((_Protection == FormsProtectionEnum.All) || (_Protection == FormsProtectionEnum.Encryption))
- {
- buf = MachineKeySection.EncryptOrDecryptData(true, buf, null, 0, buf.Length, IVType.Random);
- }
- if (!hexEncodedTicket)
- {
- return HttpServerUtility.UrlTokenEncode(buf);
- }
- return MachineKeySection.ByteArrayToHexString(buf, 0);
- }
加密利用非對稱加密技術,其中的machinekey來自於web.config。
- <machineKey validationKey="1E7A0332AD3930B95A82A1BFF82B041791366BD14DE8390B0B7EF3E7B05" decryptionKey="702BD19727054E63574" validation="SHA1"/>
3.index.aspx
- if (!User.Identity.IsAuthenticated)
- {
- Response.Redirect("/login.aspx?ReturnUrl=/index.aspx");
- }
- Response.Write("用戶名:" + User.Identity.Name );
當登陸成功后User.Identity.IsAuthenticated為true,User.Identity.Name為上述三種方法設置的username。
上面的這一切信息是“自動的”,至少我們不用編寫人任何代碼即可得到。太神奇了。記住:沒有自動的東西。要么自己寫要么是別的幫我們處理的。微軟一向助開發人員為快樂之本,一定是他干的。
-
private void OnAuthenticate(FormsAuthenticationEventArgs e) - {
- HttpCookie cookie = null;
- if (this._eventHandler != null)
- {
- this._eventHandler(this, e);
- }
- if (e.Context.User == null)
- {
- if (e.User != null)
- {
- e.Context.SetPrincipalNoDemand(e.User);
- }
- else
- {
- FormsAuthenticationTicket tOld = null;
- bool cookielessTicket = false;
- try
- {
- tOld = ExtractTicketFromCookie(e.Context, FormsAuthentication.FormsCookieName, out cookielessTicket);
- }
- catch
- {
- tOld = null;
- }
- if ((tOld != null) && !tOld.Expired)
- {
- FormsAuthenticationTicket ticket = tOld;
- if (FormsAuthentication.SlidingExpiration)
- {
- ticket = FormsAuthentication.RenewTicketIfOld(tOld);
- }
- e.Context.SetPrincipalNoDemand(new GenericPrincipal(new FormsIdentity(ticket), new string[0]));
- if (!cookielessTicket && !ticket.CookiePath.Equals("/"))
- {
- cookie = e.Context.Request.Cookies[FormsAuthentication.FormsCookieName];
- if (cookie != null)
- {
- cookie.Path = ticket.CookiePath;
- }
- }
- if (ticket != tOld)
- {
- if ((cookielessTicket && (ticket.CookiePath != "/")) && (ticket.CookiePath.Length > 1))
- {
- ticket = new FormsAuthenticationTicket(ticket.Version, ticket.Name, ticket.IssueDate, ticket.Expiration, ticket.IsPersistent, ticket.UserData, "/");
- }
- string cookieValue = FormsAuthentication.Encrypt(ticket);
- if (cookielessTicket)
- {
- e.Context.CookielessHelper.SetCookieValue('F', cookieValue);
- e.Context.Response.Redirect(e.Context.Request.PathWithQueryString);
- }
- else
- {
- if (cookie != null)
- {
- cookie = e.Context.Request.Cookies[FormsAuthentication.FormsCookieName];
- }
- if (cookie == null)
- {
- cookie = new HttpCookie(FormsAuthentication.FormsCookieName, cookieValue);
- cookie.Path = ticket.CookiePath;
- }
- if (ticket.IsPersistent)
- {
- cookie.Expires = ticket.Expiration;
- }
- cookie.Value = cookieValue;
- cookie.Secure = FormsAuthentication.RequireSSL;
- cookie.HttpOnly = true;
- if (FormsAuthentication.CookieDomain != null)
- {
- cookie.Domain = FormsAuthentication.CookieDomain;
- }
- e.Context.Response.Cookies.Remove(cookie.Name);
- e.Context.Response.Cookies.Add(cookie);
- }
- }
- }
-
} - }
- }
- private static FormsAuthenticationTicket ExtractTicketFromCookie(HttpContext context, string name, out bool cookielessTicket)
- {
- FormsAuthenticationTicket ticket = null;
- string encryptedTicket = null;
- FormsAuthenticationTicket ticket2;
- bool flag = false;
- bool flag2 = false;
- try
- {
- try
- {
- cookielessTicket = CookielessHelperClass.UseCookieless(context, false, FormsAuthentication.CookieMode);
- if (cookielessTicket)
- {
- encryptedTicket = context.CookielessHelper.GetCookieValue('F');
- }
- else
- {
- HttpCookie cookie = context.Request.Cookies[name];
- if (cookie != null)
- {
- encryptedTicket = cookie.Value;
- }
- }
- if ((encryptedTicket != null) && (encryptedTicket.Length > 1))
- {
- try
- {
- ticket = FormsAuthentication.Decrypt(encryptedTicket);
- }
- catch
- {
- if (cookielessTicket)
- {
- context.CookielessHelper.SetCookieValue('F', null);
- }
- else
- {
- context.Request.Cookies.Remove(name);
- }
- flag2 = true;
- }
- if (ticket == null)
- {
- flag2 = true;
- }
- if (((ticket != null) && !ticket.Expired) && ((cookielessTicket || !FormsAuthentication.RequireSSL) || context.Request.IsSecureConnection))
- {
- return ticket;
- }
- if ((ticket != null) && ticket.Expired)
- {
- flag = true;
- }
- ticket = null;
- if (cookielessTicket)
- {
- context.CookielessHelper.SetCookieValue('F', null);
- }
- else
- {
- context.Request.Cookies.Remove(name);
- }
- }
- if (FormsAuthentication.EnableCrossAppRedirects)
- {
- encryptedTicket = context.Request.QueryString[name];
- if ((encryptedTicket != null) && (encryptedTicket.Length > 1))
- {
- if (!cookielessTicket && (FormsAuthentication.CookieMode == HttpCookieMode.AutoDetect))
- {
- cookielessTicket = CookielessHelperClass.UseCookieless(context, true, FormsAuthentication.CookieMode);
- }
- try
- {
- ticket = FormsAuthentication.Decrypt(encryptedTicket);
- }
- catch
- {
- flag2 = true;
- }
- if (ticket == null)
- {
- flag2 = true;
- }
- }
- if ((ticket == null) || ticket.Expired)
- {
- encryptedTicket = context.Request.Form[name];
- if ((encryptedTicket != null) && (encryptedTicket.Length > 1))
- {
- if (!cookielessTicket && (FormsAuthentication.CookieMode == HttpCookieMode.AutoDetect))
- {
- cookielessTicket = CookielessHelperClass.UseCookieless(context, true, FormsAuthentication.CookieMode);
- }
- try
- {
- ticket = FormsAuthentication.Decrypt(encryptedTicket);
- }
- catch
- {
- flag2 = true;
- }
- if (ticket == null)
- {
- flag2 = true;
- }
- }
- }
- }
- if ((ticket == null) || ticket.Expired)
- {
- if ((ticket != null) && ticket.Expired)
- {
- flag = true;
- }
- return null;
- }
- if (FormsAuthentication.RequireSSL && !context.Request.IsSecureConnection)
- {
- throw new HttpException(System.Web.SR.GetString("Connection_not_secure_creating_secure_cookie"));
- }
- if (cookielessTicket)
- {
- if (ticket.CookiePath != "/")
- {
- ticket = new FormsAuthenticationTicket(ticket.Version, ticket.Name, ticket.IssueDate, ticket.Expiration, ticket.IsPersistent, ticket.UserData, "/");
- encryptedTicket = FormsAuthentication.Encrypt(ticket);
- }
- context.CookielessHelper.SetCookieValue('F', encryptedTicket);
- string url = FormsAuthentication.RemoveQueryStringVariableFromUrl(context.Request.PathWithQueryString, name);
- context.Response.Redirect(url);
- }
- else
- {
- HttpCookie cookie2 = new HttpCookie(name, encryptedTicket);
- cookie2.HttpOnly = true;
- cookie2.Path = ticket.CookiePath;
- if (ticket.IsPersistent)
- {
- cookie2.Expires = ticket.Expiration;
- }
- cookie2.Secure = FormsAuthentication.RequireSSL;
- if (FormsAuthentication.CookieDomain != null)
- {
- cookie2.Domain = FormsAuthentication.CookieDomain;
- }
- context.Response.Cookies.Remove(cookie2.Name);
- context.Response.Cookies.Add(cookie2);
- }
- ticket2 = ticket;
- }
- finally
- {
- if (flag2)
- {
- WebBaseEvent.RaiseSystemEvent(null, 0xfa5, 0xc419);
- }
- else if (flag)
- {
- WebBaseEvent.RaiseSystemEvent(null, 0xfa5, 0xc41a);
- }
- }
- }
- catch
- {
- throw;
- }
- return ticket2;
- }
-
internal void SetPrincipalNoDemand(IPrincipal principal, bool needToSetNativePrincipal) - {
- this._user = principal;
- if ((needToSetNativePrincipal && this._isIntegratedPipeline) && (this._notificationContext.CurrentNotification == RequestNotification.AuthenticateRequest))
- {
- IntPtr zero = IntPtr.Zero;
- try
- {
- IIS7WorkerRequest request = this._wr as IIS7WorkerRequest;
- if (principal != null)
- {
- GCHandle handle = GCHandle.Alloc(principal);
- try
- {
- zero = GCHandle.ToIntPtr(handle);
- request.SetPrincipal(principal, zero);
- return;
- }
- catch
- {
- zero = IntPtr.Zero;
- if (handle.IsAllocated)
- {
- handle.Free();
- }
- throw;
- }
- }
- request.SetPrincipal(null, IntPtr.Zero);
- }
- finally
- {
- if (this._pManagedPrincipal != IntPtr.Zero)
- {
- GCHandle handle2 = GCHandle.FromIntPtr(this._pManagedPrincipal);
- if (handle2.IsAllocated)
- {
- handle2.Free();
- }
- }
- this._pManagedPrincipal = zero;
- }
- }
- }
這段代碼邏輯挺簡單的:
1.得到保存認證信息的cookie
2.解密
3.生成Principa Identity信息
4.賦值給context.user
這樣我們訪問User.Identity.IsAuthenticated
User.Identity.Name就自動有值了。