asp.net 用JWT来实现token以此取代Session
先说一下为什么要写这一篇博客吧,其实个人有关asp.net 会话管理的了解也就一般,这里写出来主要是请大家帮我分析分析这个思路是否正确。我以前有些有关Session的也整理如下:
你的项目真的需要Session吗? redis保存session性能怎么样?
asp.net mvc Session RedisSessionStateProvider锁的实现
大型Web 网站 Asp.net Session过期你怎么办
Asp.net Session认识加强-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<MemberInfo>(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 == )
{
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<string, int>();
var keys = SessionStateManage.Redis.GetReadServer().Keys(SessionStateManage.Redis.Database, pattern: ConfigUtil.ApplicationName + "*").ToArray();
int count = ;
List<RedisKey> tempKeys = new List<RedisKey>();
for (int i = ; i < keys.Count(); i++)
{
tempKeys.Add(keys[i]);
count++;
if (count > || i == keys.Count() - )
{
var vals = SessionStateManage.Redis.StringGet(tempKeys.ToArray()).ToList();
vals.ForEach(x =>
{
string[] acs = x.ToString().Split('-');
if (acs != null && acs.Length == )
{
accounts.TryAdd(acs[], ConvertUtil.ToInt(acs[]));
}
});
tempKeys.Clear();
count = ;
} }
首先需要读取当前需要读取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 ?? ;
option.DefaultDatabase = option.DefaultDatabase + ;
redis = RedisUtil.GetDatabase(option.ToString(true));
SessionTimeOut = ;
} 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<MemberInfo>(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(-) };
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<T>(string obj)
{
string token = JsonWebToken.Decode(obj, jwtKey).ToString();
if (!string.IsNullOrEmpty(token))
{
return JsonConvert.DeserializeObject<T>(token);
}
return default(T);
}
} public enum JwtHashAlgorithm
{
RS256,
HS384,
HS512
} public class JsonWebToken
{
private static Dictionary<JwtHashAlgorithm, Func<byte[], byte[], byte[]>> HashAlgorithms; static JsonWebToken()
{
HashAlgorithms = new Dictionary<JwtHashAlgorithm, Func<byte[], byte[], byte[]>>
{
{ 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<string>();
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[];
var payload = parts[];
byte[] crypto = Base64UrlDecode(parts[]); 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('=')[]; // 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 % ) // Pad with trailing '='s
{
case : break; // No pad chars in this case
case : output += "=="; break; // Two pad chars
case : output += "="; break; // One pad char
default: throw new System.Exception("Illegal base64url string!");
}
var converted = Convert.FromBase64String(output); // Standard base64 decoder
return converted;
}
}
asp.net 用JWT来实现token以此取代Session的更多相关文章
- 在ASP.NET Core中实现一个Token base的身份认证
注:本文提到的代码示例下载地址> How to achieve a bearer token authentication and authorization in ASP.NET Core 在 ...
- ABP从入门到精通(5):使用基于JWT标准的Token访问WebApi
项目:asp.net zero 4.2.0 .net core(1.1) 版本 我们做项目的时候可能会遇到需要提供api给app调用,ABP动态生成的WebApi提供了方便的基于JWT标准的Token ...
- 【翻译】asp.net core2.0中的token认证
原文地址:https://developer.okta.com/blog/2018/03/23/token-authentication-aspnetcore-complete-guide token ...
- JWT(JSON Web Token) 【转载】
JWT(JSON Web Token) 什么叫JWTJSON Web Token(JWT)是目前最流行的跨域身份验证解决方案. 一般来说,互联网用户认证是这样子的. 1.用户向服务器发送用户名和密码. ...
- ABP从入门到精通(4):使用基于JWT标准的Token访问WebApi
项目:asp.net zero 4.2.0 .net core(1.1) 版本 我们做项目的时候可能会遇到需要提供api给app调用,ABP动态生成的WebApi提供了方便的基于JWT标准的Token ...
- 温故知新,.Net Core遇见JWT(JSON Web Token)授权机制方案
什么是JWT JWT (JSON Web Token) 是一个开放标准,它定义了一种以紧凑和自包含的方法,用于在双方之间安全地传输编码为JSON对象的信息. 因此,简单来说,它是JSON格式的加密字符 ...
- 【JWT】JWT+HA256加密 Token验证
目录 Token验证 传统的Token验证 JWT+HA256验证 回到顶部 Token验证 最近了解下基于 Token 的身份验证,跟大伙分享下.很多大型网站也都在用,比如 Facebook,Twi ...
- Java JWT: JSON Web Token
Java JWT: JSON Web Token for Java and Android JJWT aims to be the easiest to use and understand libr ...
- [认证授权] 2.OAuth2授权(续) & JWT(JSON Web Token)
1 RFC6749还有哪些可以完善的? 1.1 撤销Token 在上篇[认证授权] 1.OAuth2授权中介绍到了OAuth2可以帮我们解决第三方Client访问受保护资源的问题,但是只提供了如何获得 ...
随机推荐
- CSS伪元素before、after妙用
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...
- 使用mybatis-spring-boot-starter如何打印sql语句
只需要将接口文件的日志设置为debug即可. 例如你的mapper接口所在的文件夹是 com.demo.mapper 那么在application.properties配置文件中添加 logging. ...
- django----数据库表设计
设计表时注意的几点: 1. nid = models.AutoField(primary_key=True) #如果不指定django会默认加上id的 nid = models.BigA ...
- python 全栈开发,Day126(创业故事,软件部需求,内容采集,显示内容图文列表,MongoDB数据导入导出JSON)
作业讲解 下载代码: HBuilder APP和flask后端登录 链接:https://pan.baidu.com/s/1eBwd1sVXTNLdHwKRM2-ytg 密码:4pcw 如何打开APP ...
- struts2的文件上传和文件下载
实现使用Struts2文件上传和文件下载: 注意点: (1)对应表单的file1和私有成员变量的名称必须一致 <input type="file" name="fi ...
- [转] Anaconda使用总结
机器上的不同用户完全可以安装.配置自己的Anaconda,不会互相影响. 对于Mac.Linux系统,Anaconda安装好后,实际上就是在主目录下多了个文件夹(~/anaconda)而已,Windo ...
- 【Java】 剑指offer(57-1) 和为s的两个数字
本文参考自<剑指offer>一书,代码采用Java语言. 更多:<剑指Offer>Java实现合集 题目 输入一个递增排序的数组和一个数字s,在数组中查找两个数,使得它 ...
- 010 Spark中的监控----日志聚合的配置,以及REST Api
一:History日志聚合的配置 1.介绍 Spark的日志聚合功能不是standalone模式独享的,是所有运行模式下都会存在的情况 默认情况下历史日志是保存到tmp文件夹中的 2.参考官网的知识点 ...
- Python6 - 函数总结
一.函数的基本知识 定义: 函数是指将一组语句的集合通过一个名字(函数名)封装起来,要想执行这个函数,只需调用其函数名即可 特性: 减少重复代码 使程序变的可扩展 使程序变得易维护 1.1函数定义规则 ...
- 基于Java的REST架构风格及接口安全性设计的讨论
1.REST即表现层状态传递(Representational [,rɛprɪzɛn'teʃnl] State Transfer,简称REST). (1)REST名词解释: 通俗来讲就是资源在网络中以 ...