使用ASP.NET重新发明我的身份验证策略
目前,我为我的网站使用自定义编写的身份验证代码,该代码基于.NET构建.我没有采用标准的Forms Auth路由,因为我能找到的所有示例都与WebForms紧密集成,我没有使用它.出于所有意图和目的,我拥有所有静态
HTML,并且任何逻辑都是通过
Javascript和Web服务调用完成的.登录,注销和创建新帐户等操作即使不离开页面也可以完成.
以下是它现在的工作原理:在数据库中,我有一个用户ID,一个安全ID和一个会话ID.这三个都是UUID,前两个永远不会改变.每次用户登录时,我都会检查用户表中是否有与该用户名和散列密码匹配的行,并将会话ID更新为新的UUID.然后我创建一个cookie,它是所有三个UUID的序列化表示.在任何安全的Web服务调用中,我反序列化该cookie,以确保users表中有一行包含这3个UUID.这是一个相当简单的系统并且运行良好,但是我并不喜欢用户一次只能登录一个客户端这一事实.当我创建移动设备和平板电脑应用时,它会引发问题,如果他们有多台计算机或网络浏览器,就会产生问题.出于这个原因,我正在考虑抛弃这个系统并采用新的方法.自从我多年前写这篇文章以来,我认为可能会有更多推荐. 我一直在阅读.NET Framework中的FormsAuthentication类,它处理auth cookie,并作为HttpModule运行以验证每个请求.我想知道我是否可以在我的新设计中利用这一点. 看起来像cookie是无状态的,并且不必在数据库中跟踪会话.这是通过使用服务器上的私钥加密cookie来实现的,也可以在Web服务器集群之间共享.如果我这样做: FormsAuthentication.SetAuthCookie("Bob",true); 然后在以后的请求中,我可以放心,Bob确实是一个有效的用户,因为如果不是不可能伪造的话,cookie将非常困难. 使用FormsAuthentication类替换当前的身份验证模型是否明智?我不是在数据库中有会话ID列,而是依靠加密的cookie来表示有效的会话. 是否有第三方/开源.NET身份验证框架可能更适合我的架构? 这种身份验证机制是否会对移动和平板电脑客户端上运行的代码(例如iPhone应用程序或Windows 8 Surface应用程序)造成任何不满?我认为只要这些应用程序可以处理cookie,这将有效.谢谢! 解决方法
由于我没有得到任何回复,我决定自己动手.首先,我发现了一个以算法无关的方式实现会话cookie的
open source project.我用它作为实现类似处理程序的起点.
我在内置ASP.NET实现中遇到的一个问题是AppHarbor实现中的类似限制,会话只能通过字符串用户名来键入.我希望能够存储任意数据以识别用户,例如数据库中的UUID以及他们的登录名.由于我现有的许多代码都假设cookie中有这些数据,如果这些数据不再可用,则需要进行大量的重构.另外,我喜欢能够存储基本用户信息而无需访问数据库的想法. 正如this open issue所指出的,AppHarbor项目的另一个问题是加密算法未经验证.这并不完全正确,因为AppHarbor与算法无关,但是请求示例项目应该显示如何使用PBKDF2.因此,我决定使用此算法(在.NET Framework中通过Rfc2898DeriveBytes class实现)码. 这就是我能想到的.对于想要实现自己的会话管理的人来说,这是一个起点,所以请随意将它用于您认为合适的任何目的. using System; using System.IO; using System.Linq; using System.Runtime.Serialization.Formatters.Binary; using System.Security; using System.Security.Cryptography; using System.Security.Principal; using System.Web; namespace AuthTest { [Serializable] public class AuthIdentity : IIdentity { public Guid Id { get; private set; } public string Name { get; private set; } public AuthIdentity() { } public AuthIdentity(Guid id,string name) { Id = id; Name = name; } public string AuthenticationType { get { return "CookieAuth"; } } public bool IsAuthenticated { get { return Id != Guid.Empty; } } } [Serializable] public class AuthToken : IPrincipal { public IIdentity Identity { get; set; } public bool IsInRole(string role) { return false; } } public class AuthModule : IHttpModule { static string COOKIE_NAME = "AuthCookie"; //Note: Change these two keys to something else (VALIDATION_KEY is 72 bytes,ENCRYPTION_KEY is 64 bytes) static string VALIDATION_KEY = @"MkMvk1JL/ghytaERtl6A25iTf/ABC2MgPsFlEbASJ5SX4DiqnDN3CjV7HXQI0GBOGyA8nHjSVaAJXNEqrKmOMg=="; static string ENCRYPTION_KEY = @"QQJYW8ditkzaUFppCJj+DcCTc/H9TpnSRQrLGBQkhy/jnYjqF8iR6do9NvI8PL8MmniFvdc21sTuKkw94jxID4cDYoqr7JDj"; static byte[] key; static byte[] iv; static byte[] valKey; public void Dispose() { } public void Init(HttpApplication context) { context.AuthenticateRequest += OnAuthenticateRequest; context.EndRequest += OnEndRequest; byte[] bytes = Convert.FromBase64String(ENCRYPTION_KEY); //72 bytes (8 for salt,64 for key) byte[] salt = bytes.Take(8).ToArray(); byte[] pw = bytes.Skip(8).ToArray(); Rfc2898DeriveBytes k1 = new Rfc2898DeriveBytes(pw,salt,1000); key = k1.GetBytes(16); iv = k1.GetBytes(8); valKey = Convert.FromBase64String(VALIDATION_KEY); //64 byte validation key to prevent tampering } public static void SetCookie(AuthIdentity token,bool rememberMe = false) { //Base64 encode token var formatter = new BinaryFormatter(); MemoryStream stream = new MemoryStream(); formatter.Serialize(stream,token); byte[] buffer = stream.GetBuffer(); byte[] encryptedBytes = EncryptCookie(buffer); string str = Convert.ToBase64String(encryptedBytes); var cookie = new HttpCookie(COOKIE_NAME,str); cookie.HttpOnly = true; if (rememberMe) { cookie.Expires = DateTime.Today.AddDays(100); } HttpContext.Current.Response.Cookies.Add(cookie); } public static void Logout() { HttpContext.Current.Response.Cookies.Remove(COOKIE_NAME); HttpContext.Current.Response.Cookies.Add(new HttpCookie(COOKIE_NAME,"") { Expires = DateTime.Today.AddDays(-1) }); } private static byte[] EncryptCookie(byte[] rawBytes) { TripleDES des = TripleDES.Create(); des.Key = key; des.IV = iv; MemoryStream encryptionStream = new MemoryStream(); CryptoStream encrypt = new CryptoStream(encryptionStream,des.CreateEncryptor(),CryptoStreamMode.Write); encrypt.Write(rawBytes,rawBytes.Length); encrypt.FlushFinalBlock(); encrypt.Close(); byte[] encBytes = encryptionStream.ToArray(); //Add validation hash (compute hash on unencrypted data) HMACSHA256 hmac = new HMACSHA256(valKey); byte[] hash = hmac.ComputeHash(rawBytes); //Combine encrypted bytes and validation hash byte[] ret = encBytes.Concat<byte>(hash).ToArray(); return ret; } private static byte[] DecryptCookie(byte[] encBytes) { TripleDES des = TripleDES.Create(); des.Key = key; des.IV = iv; HMACSHA256 hmac = new HMACSHA256(valKey); int valSize = hmac.HashSize / 8; int msgLength = encBytes.Length - valSize; byte[] message = new byte[msgLength]; byte[] valBytes = new byte[valSize]; Buffer.BlockCopy(encBytes,message,msgLength); Buffer.BlockCopy(encBytes,msgLength,valBytes,valSize); MemoryStream decryptionStreamBacking = new MemoryStream(); CryptoStream decrypt = new CryptoStream(decryptionStreamBacking,des.CreateDecryptor(),CryptoStreamMode.Write); decrypt.Write(message,msgLength); decrypt.Flush(); byte[] decMessage = decryptionStreamBacking.ToArray(); //Verify key matches byte[] hash = hmac.ComputeHash(decMessage); if (valBytes.SequenceEqual(hash)) { return decMessage; } throw new SecurityException("Auth Cookie appears to have been tampered with!"); } private void OnAuthenticateRequest(object sender,EventArgs e) { var context = ((HttpApplication)sender).Context; var cookie = context.Request.Cookies[COOKIE_NAME]; if (cookie != null && cookie.Value.Length > 0) { try { var formatter = new BinaryFormatter(); MemoryStream stream = new MemoryStream(); var bytes = Convert.FromBase64String(cookie.Value); var decBytes = DecryptCookie(bytes); stream.Write(decBytes,decBytes.Length); stream.Seek(0,SeekOrigin.Begin); AuthIdentity auth = formatter.Deserialize(stream) as AuthIdentity; AuthToken token = new AuthToken() { Identity = auth }; context.User = token; //Renew the cookie for another 100 days (TODO: Should only renew if cookie was originally set to persist) context.Response.Cookies[COOKIE_NAME].Value = cookie.Value; context.Response.Cookies[COOKIE_NAME].Expires = DateTime.Today.AddDays(100); } catch { } //Ignore any errors with bad cookies } } private void OnEndRequest(object sender,EventArgs e) { var context = ((HttpApplication)sender).Context; var response = context.Response; if (response.Cookies.Keys.Cast<string>().Contains(COOKIE_NAME)) { response.Cache.SetCacheability(HttpCacheability.NoCache,"Set-Cookie"); } } } } 另外,请确保在web.config文件中包含以下模块: <httpModules> <add name="AuthModule" type="AuthTest.AuthModule" /> </httpModules> 在您的代码中,您可以使用以下命令查找当前登录的用户: var id = HttpContext.Current.User.Identity as AuthIdentity; 并设置auth cookie如下: AuthIdentity token = new AuthIdentity(Guid.NewGuid(),"Mike"); AuthModule.SetCookie(token,false); (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |
- ASP.Net标签值在JQuery中发生了变化,但在回发时没有变化
- ASP.net网络资源与图像
- asp.net-identity – 使用SQL Server而不是LocalDB的Web AP
- asp.net-mvc – 编译MVC并预编译视图并部署到Azure WebRole
- 覆盖ASP.NET WebMethod参数的DateTime序列化
- 如何配置asp.net与.net 4.5
- 从ASP.NET应用程序使用Active Directory时,DirectoryServic
- asp.net – [DataType(DataType.EmailAddress)]和[EmailAdd
- ASP.NET异步任务 – 如何使用Page.RegisterAsyncTask使用We
- asp.net-mvc-3 – 坚持使用asp.net mvc 3.0脚手架,以防多对
- asp.net-mvc – 如何将数据属性放在Ajax.BeginFo
- asp.net-mvc-3 – 在远程部署MVC3时获取“CS0103
- asp.net-mvc – ReadOnly属性在ASP.NET MVC模型中
- asp.net-mvc – 为什么HttpPostedFile不像广告和
- asp.net-mvc – ASP.Net MVC不显眼的日期范围验证
- asp.net-core – 具有不同root的ASP.Net核心反向
- asp.net-mvc – 如何使用Autofac将属性“绑定”到
- 在ASP.Net中防止SQL注入
- asp.net-mvc – .NET MVC不显眼的验证和自定义模
- 当调用ASP.NET System.Web.HttpResponse.End()时