本文将介绍.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就自动有值了。