以下是它现在的工作原理:在数据库中,我有一个用户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,这将有效.谢谢!
解决方法
我在内置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);
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。