博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
asp.net 用JWT来实现token以此取代Session
阅读量:5062 次
发布时间:2019-06-12

本文共 15304 字,大约阅读时间需要 51 分钟。

先说一下为什么要写这一篇博客吧,其实个人有关asp.net 会话管理的了解也就一般,这里写出来主要是请大家帮我分析分析这个思路是否正确。我以前有些有关Session的也整理如下:

先简单的说一下问题所在,目前项目是用RedisSessionStateProvider来管理我们的会话,同时我们的业务数据有一部分也存放在redis里面,并且在一个redis实例里面。 项目采用asp.net api +单页面程序。就我个人而言我是很讨厌Session 过期的时候给你弹一个提示让你重新登录这种情况。京东和淘宝都不干这事。。。。,系统还需要有 单点登录和在线统计功能,以下说说我的思路:

1.如果用纯净的JWT来实现,客户端必须改code,因为jwt实现可以放在http请求的body或者header,无论整么放都需要修改前端js的code,所以我决定用cookie在存放对应的数据,因为cookie是浏览器管理的;注意一下jwt放在header在跨域的时候会走复杂跨域请求哦。

2.再用统计计划用redis的key来做,通过查找可以的个数确认在线的人数,key的value将存放用户名和级别

3.单点登录还是用redis再做,把当前用户的session id存起来,和当前http请求的session id对比,不同就踢出去。

运行结果如下:

进入login页面,我会清空当前域的所有cookie,然后分配一个session id

输入用户名和密码进入到一个 协议页面,会增加一个TempMemberId的cookie

注意这里的TempMemberId是jwt的格式,接受协议后会删除该cookie,并把当前session Id作为cookie 的key把真正的值赋给它

设置该cookie的code如下:

public static void SetLogin(int memberId, MemberInfo member)        {            HttpCookie sessionCookie = HttpContext.Current.Request.Cookies[CookieName];            if (sessionCookie != null && !string.IsNullOrEmpty(sessionCookie.Value))            {                string token = Encode(member);                HttpCookie membercookie = new HttpCookie(sessionCookie.Value, token) { HttpOnly = true };                HttpContext.Current.Response.SetCookie(membercookie);                string loginKey = ConfigUtil.ApplicationName + "-" + member.MemberId.ToString();                string memberKey = $"{member.MemberLevel.ToString()}-{member.Account}";                redis.StringSet(loginKey, memberKey, TimeSpan.FromMinutes(SessionTimeOut));            }        }

首先获取需要写入cookie的key(session id的值),也就是sessionCookie的value,把当前MemberInfo实例通过jwt的方式转换为字符串,把它写入到cookie,然后在写redis,一个用户在多个浏览器登录,但是他的memberId是一样的,所以redis只有一条记录,这一条记录用于统计在线人数,value值是 级别-用户名。真正调用的地方如下:

SessionStateManage.SetLogin(memberId, GetMemberFromDB(memberId));  GetMemberFromDB方法从数据库检索数据并返回为MemberInfo实例

SessionStateManage.RemoveCookie(SessionStateManage.CookieName + "_TempMemberId");

string loginGuid = HttpContext.Current.Request.Cookies[SessionStateManage.CookieName]; 返回的就是我们sesssion id

string redisKey = RedisConsts.AccountMember + memberId;
RedisUtil.GetDatabase().HashSet(redisKey, "LoginGuid", loginGuid); 一个member只记录最后一个login的session id

那么加载用户信息的code如下:

public static MemberInfo GetUser()        {            MemberInfo member = null;            HttpCookie sessionCookie = HttpContext.Current.Request.Cookies[CookieName];            if (sessionCookie != null && !string.IsNullOrEmpty(sessionCookie.Value))            {                HttpCookie memberCookie = HttpContext.Current.Request.Cookies[sessionCookie.Value];                member = Decode
(memberCookie.Value); if (member != null) { string loginKey = ConfigUtil.ApplicationName + ":" + member.MemberId; // redis.KeyExpire(loginKey, TimeSpan.FromMinutes(SessionTimeOut)); string memberKey = $"{member.MemberLevel.ToString()}-{member.Account}"; redis.StringSet(loginKey, memberKey, TimeSpan.FromMinutes(SessionTimeOut)); } } HttpContext.Current.Items[RedisConsts.SessionMemberInfo] = member; return member; }

首先需要获取jwt的原始数据,存放在memberCookie里面,然后解码为MemberInfo实例,并且保存到 HttpContext.Current.Items里面(主要是维持以前的code不变),同时需要刷新 redis 里面对应key的过期时间

public static string Account        {            get            {                //return ConvertUtil.ToString(HttpContext.Current.Session["Account"], string.Empty);                var memberinfo = HttpContext.Current.Items[RedisConsts.SessionMemberInfo] as MemberInfo;                return memberinfo == null ? string.Empty : memberinfo.Account;            }            set            {                //HttpContext.Current.Session["Account"] = value;                ExceptionUtil.ThrowMessageException("不能给session赋值");            }        }

看了这个code大家知道为什么需要保存到HttpContext.Current.Items里面了。

protected override void OnAuthentication(AuthenticationContext filterContext)        {            object[] nonAuthorizedAttributes = filterContext.ActionDescriptor.GetCustomAttributes(typeof(NonAuthorizedAttribute), false);            if (nonAuthorizedAttributes.Length == 0)            {                SessionStateManage.GetUser();                //账户踢出检测                string loginGuid = RedisUtil.GetDatabase().HashGet(RedisConsts.AccountMember + memberId, "LoginGuid");                if (loginGuid != SessionUtil.LoginGuid)                {                    //.......您的账号已在别处登录;                }              }        }

我们在Controller里面OnAuthentication方法调用 SessionStateManage.GetUser();方法,检查redis里面存放的session id和当前请求的session id是否一致,不一致踢出去。把MemberInfo放到HttpContext.Current.Items里面来调用也算是历史遗留问题,个人更建议把MemberInfo实例作为Controller的属性来访问

在线统计:

var accounts = new Dictionary
(); var keys = SessionStateManage.Redis.GetReadServer().Keys(SessionStateManage.Redis.Database, pattern: ConfigUtil.ApplicationName + "*").ToArray(); int count = 0; List
tempKeys = new List
(); for (int i = 0; i < keys.Count(); i++) { tempKeys.Add(keys[i]); count++; if (count > 1000 || i == keys.Count() - 1) { var vals = SessionStateManage.Redis.StringGet(tempKeys.ToArray()).ToList(); vals.ForEach(x => { string[] acs = x.ToString().Split('-'); if (acs != null && acs.Length == 2) { accounts.TryAdd(acs[1], ConvertUtil.ToInt(acs[0])); } }); tempKeys.Clear(); count = 0; } }

首先需要读取当前需要读取key的集合,记住在redis的Keys方法带参数pattern的性能要低一点,它需要把key读出来然后再过滤。如果运维能确定当前database的key都是需要读取的那么就可以不用pattern参数。为了提高性能StringGet一次可以读取1000个key,这里设计为字符串的key而不是hash的原因就是读取方便。在使用redis个人不建议用异步和所谓的多线程,因为redis服务器是单线程,所以多线程可能感觉和测试都要快一些,但是redis一直忙于处理你当前的请求,别的请求就很难处理了

完整的会话处理code如下:

public class TempMemberInfo    {        public int TempMemberId { set; get; }    }    public class MemberInfo    {        public int MemberId { set; get; }        public string Account { set; get; }        public int ParentId { set; get; }        public int CompanyId { set; get; }        public int MemberLevel { set; get; }        public int IsSubAccount { set; get; }        public int AgentId { set; get; }        public int BigAgentId { set; get; }        public int ShareHolderId { set; get; }        public int BigShareHolderId { set; get; }        public int DirectorId { set; get; }    }    public class SessionStateManage    {        static RedisDatabase redis;        static string jwtKey = "SevenStarKey";        static SessionStateManage()        {            CookieName = ConfigUtil.CookieName;            string conStr = ConfigUtil.RedisConnectionString;            ConfigurationOptions option = ConfigurationOptions.Parse(conStr);            int databaseId = option.DefaultDatabase ?? 0;            option.DefaultDatabase = option.DefaultDatabase + 1;            redis = RedisUtil.GetDatabase(option.ToString(true));            SessionTimeOut = 20;        }        public static string CookieName { get; private set; }        public static int SessionTimeOut { get; set; }        public static RedisDatabase Redis        {            get            {                return redis;            }        }        public static void InitCookie()        {            RemoveCookie();            string cookiememberId = (new SessionIDManager()).CreateSessionID(HttpContext.Current);            HttpCookie cookieId = new HttpCookie(CookieName, cookiememberId) { HttpOnly = true };            HttpContext.Current.Response.SetCookie(cookieId);        }        public static void SetLogin(int memberId, MemberInfo member)        {            HttpCookie sessionCookie = HttpContext.Current.Request.Cookies[CookieName];            if (sessionCookie != null && !string.IsNullOrEmpty(sessionCookie.Value))            {                string token = Encode(member);                HttpCookie membercookie = new HttpCookie(sessionCookie.Value, token) { HttpOnly = true };                HttpContext.Current.Response.SetCookie(membercookie);                string loginKey = ConfigUtil.ApplicationName + "-" + member.MemberId.ToString();                string memberKey = $"{member.MemberLevel.ToString()}-{member.Account}";                redis.StringSet(loginKey, memberKey, TimeSpan.FromMinutes(SessionTimeOut));            }        }        public static MemberInfo GetUser()        {            MemberInfo member = null;            HttpCookie sessionCookie = HttpContext.Current.Request.Cookies[CookieName];            if (sessionCookie != null && !string.IsNullOrEmpty(sessionCookie.Value))            {                HttpCookie memberCookie = HttpContext.Current.Request.Cookies[sessionCookie.Value];                member = Decode
(memberCookie.Value); if (member != null) { string loginKey = ConfigUtil.ApplicationName + ":" + member.MemberId; redis.KeyExpire(loginKey, TimeSpan.FromMinutes(SessionTimeOut)); } } HttpContext.Current.Items[RedisConsts.SessionMemberInfo] = member; return member; } public static void Clear() { HttpCookie sessionCookie = HttpContext.Current.Request.Cookies[CookieName]; if (sessionCookie != null && !string.IsNullOrEmpty(sessionCookie.Value)) { HttpCookie membercookie = HttpContext.Current.Request.Cookies[sessionCookie.Value]; if (membercookie != null) { string loginKey = RedisConsts.AccountLogin + membercookie.Value; redis.KeyDelete(loginKey); } } RemoveCookie(); } public static void RemoveCookie(string key) { var cookie = new HttpCookie(key) { Expires = DateTime.Now.AddDays(-1) }; HttpContext.Current.Response.Cookies.Set(cookie); } static void RemoveCookie() { foreach (string key in HttpContext.Current.Request.Cookies.AllKeys) { RemoveCookie(key); } } public static string Encode(object obj) { return JsonWebToken.Encode(obj, jwtKey, JwtHashAlgorithm.RS256); ; } public static T Decode
(string obj) { string token = JsonWebToken.Decode(obj, jwtKey).ToString(); if (!string.IsNullOrEmpty(token)) { return JsonConvert.DeserializeObject
(token); } return default(T); } } public enum JwtHashAlgorithm { RS256, HS384, HS512 } public class JsonWebToken { private static Dictionary
> HashAlgorithms; static JsonWebToken() { HashAlgorithms = new Dictionary
> { { JwtHashAlgorithm.RS256, (key, value) => { using (var sha = new HMACSHA256(key)) { return sha.ComputeHash(value); } } }, { JwtHashAlgorithm.HS384, (key, value) => { using (var sha = new HMACSHA384(key)) { return sha.ComputeHash(value); } } }, { JwtHashAlgorithm.HS512, (key, value) => { using (var sha = new HMACSHA512(key)) { return sha.ComputeHash(value); } } } }; } public static string Encode(object payload, string key, JwtHashAlgorithm algorithm) { return Encode(payload, Encoding.UTF8.GetBytes(key), algorithm); } public static string Encode(object payload, byte[] keyBytes, JwtHashAlgorithm algorithm) { var segments = new List
(); var header = new { alg = algorithm.ToString(), typ = "JWT" }; byte[] headerBytes = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(header, Formatting.None)); byte[] payloadBytes = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(payload, Formatting.None)); //byte[] payloadBytes = Encoding.UTF8.GetBytes(@"{"iss":"761326798069-r5mljlln1rd4lrbhg75efgigp36m78j5@developer.gserviceaccount.com","scope":"https://www.googleapis.com/auth/prediction","aud":"https://accounts.google.com/o/oauth2/token","exp":1328554385,"iat":1328550785}"); segments.Add(Base64UrlEncode(headerBytes)); segments.Add(Base64UrlEncode(payloadBytes)); var stringToSign = string.Join(".", segments.ToArray()); var bytesToSign = Encoding.UTF8.GetBytes(stringToSign); byte[] signature = HashAlgorithms[algorithm](keyBytes, bytesToSign); segments.Add(Base64UrlEncode(signature)); return string.Join(".", segments.ToArray()); } public static object Decode(string token, string key) { return Decode(token, key, true); } public static object Decode(string token, string key, bool verify) { var parts = token.Split('.'); var header = parts[0]; var payload = parts[1]; byte[] crypto = Base64UrlDecode(parts[2]); var headerJson = Encoding.UTF8.GetString(Base64UrlDecode(header)); var headerData = JObject.Parse(headerJson); var payloadJson = Encoding.UTF8.GetString(Base64UrlDecode(payload)); var payloadData = JObject.Parse(payloadJson); if (verify) { var bytesToSign = Encoding.UTF8.GetBytes(string.Concat(header, ".", payload)); var keyBytes = Encoding.UTF8.GetBytes(key); var algorithm = (string)headerData["alg"]; var signature = HashAlgorithms[GetHashAlgorithm(algorithm)](keyBytes, bytesToSign); var decodedCrypto = Convert.ToBase64String(crypto); var decodedSignature = Convert.ToBase64String(signature); if (decodedCrypto != decodedSignature) { throw new ApplicationException(string.Format("Invalid signature. Expected {0} got {1}", decodedCrypto, decodedSignature)); } } //return payloadData.ToString(); return payloadData; } private static JwtHashAlgorithm GetHashAlgorithm(string algorithm) { switch (algorithm) { case "RS256": return JwtHashAlgorithm.RS256; case "HS384": return JwtHashAlgorithm.HS384; case "HS512": return JwtHashAlgorithm.HS512; default: throw new InvalidOperationException("Algorithm not supported."); } } // from JWT spec private static string Base64UrlEncode(byte[] input) { var output = Convert.ToBase64String(input); output = output.Split('=')[0]; // Remove any trailing '='s output = output.Replace('+', '-'); // 62nd char of encoding output = output.Replace('/', '_'); // 63rd char of encoding return output; } // from JWT spec private static byte[] Base64UrlDecode(string input) { var output = input; output = output.Replace('-', '+'); // 62nd char of encoding output = output.Replace('_', '/'); // 63rd char of encoding switch (output.Length % 4) // Pad with trailing '='s { case 0: break; // No pad chars in this case case 2: output += "=="; break; // Two pad chars case 3: output += "="; break; // One pad char default: throw new System.Exception("Illegal base64url string!"); } var converted = Convert.FromBase64String(output); // Standard base64 decoder return converted; } }
View Code

 

转载于:https://www.cnblogs.com/majiang/p/6650459.html

你可能感兴趣的文章
Hello China操作系统STM32移植指南(一)
查看>>
cocos2dx CCEditBox
查看>>
VC++2012编程演练数据结构《8》回溯法解决迷宫问题
查看>>
第一阶段冲刺06
查看>>
WIN下修改host文件并立即生效
查看>>
十个免费的 Web 压力测试工具
查看>>
ckeditor 粘贴后去除html标签
查看>>
Mysql DISTINCT问题
查看>>
sort和sorted的区别
查看>>
UI自动化
查看>>
Elasticsearch-基础介绍及索引原理分析
查看>>
AJAX 学习笔记
查看>>
String.format(),字符拼接
查看>>
dbutils开源项目用法
查看>>
JSP获取当前日期时间
查看>>
undefined reference to `_sbrk', `_write', `_lseek', `_read'
查看>>
基于zuul 实现API 网关
查看>>
定义自己的布局RelativeLayout 绘制网格线
查看>>
redis
查看>>
Ubuntu13.04 安装 chrome
查看>>