net core使用jwt二 : 验证前台传递的token
电脑版发表于:2019/11/17 23:18
上一篇说了net core中生成jwt:
http://www.tnblog.net/aojiancc2/article/details/2815
现在说说怎么来验证前台传递的jwt,其实很简单,最主要的就是验证token的有效性和是否过期
/// <summary> /// 验证身份 验证签名的有效性 /// </summary> /// <param name="encodeJwt"></param> /// <param name="validatePayLoad">自定义各类验证; 是否包含那种申明,或者申明的值,例如:payLoad["aud"]?.ToString() == "AXJ"; </param> public bool Validate(string encodeJwt, Func<Dictionary<string, string>, bool> validatePayLoad = null) { var success = true; var jwtArr = encodeJwt.Split('.'); if (jwtArr.Length < 3)//数据格式都不对直接pass { return false; } var header = JsonConvert.DeserializeObject<Dictionary<string, string>>(Base64UrlEncoder.Decode(jwtArr[0])); var payLoad = JsonConvert.DeserializeObject<Dictionary<string, string>>(Base64UrlEncoder.Decode(jwtArr[1])); //配置文件中取出来的签名秘钥 var hs256 = new HMACSHA256(Encoding.ASCII.GetBytes(_options.Value.IssuerSigningKey)); //验证签名是否正确(把用户传递的签名部分取出来和服务器生成的签名匹配即可) success = success && string.Equals(jwtArr[2], Base64UrlEncoder.Encode(hs256.ComputeHash(Encoding.UTF8.GetBytes(string.Concat(jwtArr[0], ".", jwtArr[1]))))); if (!success) { return success;//签名不正确直接返回 } //其次验证是否在有效期内(也应该必须) var now = ToUnixEpochDate(DateTime.UtcNow); success = success && (now >= long.Parse(payLoad["nbf"].ToString()) && now < long.Parse(payLoad["exp"].ToString())); //不需要自定义验证不传或者传递null即可 if (validatePayLoad == null) return true; //再其次 进行自定义的验证 success = success && validatePayLoad(payLoad); return success; }
使用的时候直接调用这个方法就行了
[HttpGet("{token}/{page}/{rows}")] public string Get(string token, int? page, int? rows) { //不需要自定义验证的就不传递即可 bool isvilidate = tokenHelper.Validate(token); //需要额外自定义验证的就自己写 bool isvilidate2 = tokenHelper.Validate(token, a => a["iss"] == "TNBLOG" && a["aud"] == "AXJ"); if (isvilidate2 == false) { return "验证失败"; } //其他业务 return "value"; }
上面一个简单的验证和支持自定义验证的就写好了,但是我们这样我们只知道是否验证成功,不知道验证失败是为什么失败的,比如签名不对,比如过期了,特别是过期了很多时候我们可能需要把这个过期状态提醒给用户,所以我们就可以弄一个枚举,这样可以一目了然知道验证的状态
//验证jwt状态的枚举 public enum TokenType { Ok, Fail, Expired }
验证方法:
public TokenType ValidatePlus(string encodeJwt, Func<Dictionary<string, string>, bool> validatePayLoad, Action<Dictionary<string, string>> action) { var jwtArr = encodeJwt.Split('.'); if (jwtArr.Length < 3)//数据格式都不对直接pass { return TokenType.Fail; } var header = JsonConvert.DeserializeObject<Dictionary<string, string>>(Base64UrlEncoder.Decode(jwtArr[0])); var payLoad = JsonConvert.DeserializeObject<Dictionary<string, string>>(Base64UrlEncoder.Decode(jwtArr[1])); var hs256 = new HMACSHA256(Encoding.ASCII.GetBytes(_options.Value.IssuerSigningKey)); //验证签名是否正确(把用户传递的签名部分取出来和服务器生成的签名匹配即可) if (!string.Equals(jwtArr[2], Base64UrlEncoder.Encode(hs256.ComputeHash(Encoding.UTF8.GetBytes(string.Concat(jwtArr[0], ".", jwtArr[1])))))) { return TokenType.Fail; } //其次验证是否在有效期内(必须验证) var now = ToUnixEpochDate(DateTime.UtcNow); if (!(now >= long.Parse(payLoad["nbf"].ToString()) && now < long.Parse(payLoad["exp"].ToString()))) { return TokenType.Expired; } //不需要自定义验证不传或者传递null即可 if (validatePayLoad == null) { action(payLoad); return TokenType.Ok; } //再其次 进行自定义的验证 if (!validatePayLoad(payLoad)) { return TokenType.Fail; } //可能需要获取jwt摘要里边的数据,封装一下方便使用 action(payLoad); return TokenType.Ok; }
这里第三个参数我还更了一个系统委托,是这样想的,处理可以验证token,还可以顺便取一个想要的数据,当然其实这样把相关逻辑混到一起也增加代码的耦合性,当时可以提高一点效率不用在重新解析一次数据,当然这个数据也可以通前台传递过来,所以怎么用还是看实际情况,这里只是封装一下提供这样一个方法,用的时候也可以用。
使用:
[HttpGet("{token}/{page}/{rows}")] public ReturnModel<List<DTO_Article>> Get(string token, int? page, int? rows) { ReturnModel<List<DTO_Article>> returnModel = new ReturnModel<List<DTO_Article>>(); try { page = page ?? 1; rows = rows ?? 9; if (string.IsNullOrWhiteSpace(token)) { returnModel.Code = 201; returnModel.Msg = "token不能为空"; return returnModel; } string userId = ""; //验证jwt,同时取出来jwt里边的用户ID TokenType tokenType = tokenHelper.ValidatePlus(token, a => a["iss"] == "TNBLOG" && a["aud"] == "AXJ", action => { userId = action["userId"]; }); if (tokenType == TokenType.Fail) { returnModel.Code = 202; returnModel.Msg = "token验证失败"; return returnModel; } if (tokenType == TokenType.Expired) { returnModel.Code = 205; returnModel.Msg = "token已经过期"; return returnModel; } //..............其他逻辑 returnModel.Code = 200; returnModel.Msg = "访问成功!"; returnModel.Value = new List<DTO_Article>(); return returnModel; } catch (Exception ex) { returnModel.Code = 500; returnModel.Msg = "内部错误!"; return returnModel; } }
下面贴一下完整一点的TokenHelper类:
public class TokenHelper : ITokenHelper { //验证jwt状态的枚举 public enum TokenType { Ok, Fail, Expired } private IOptions<JWTConfig> _options; public TokenHelper(IOptions<JWTConfig> options) { _options = options; } /// <summary> /// 根据一个对象通过反射提供负载生成token /// </summary> /// <typeparam name="T"></typeparam> /// <param name="user"></param> /// <returns></returns> public TnToken CreateToken<T>(T user) where T : class { //携带的负载部分,类似一个键值对 List<Claim> claims = new List<Claim>(); //这里我们用反射把model数据提供给它 foreach (var item in user.GetType().GetProperties()) { object obj = item.GetValue(user); string value = ""; if (obj != null) value = obj.ToString(); claims.Add(new Claim(item.Name, value)); } //创建token return CreateToken(claims); } /// <summary> /// 根据键值对提供负载生成token /// </summary> /// <param name="keyValuePairs"></param> /// <returns></returns> public TnToken CreateToken(Dictionary<string, string> keyValuePairs) { //携带的负载部分,类似一个键值对 List<Claim> claims = new List<Claim>(); //这里我们通过键值对把数据提供给它 foreach (var item in keyValuePairs) { claims.Add(new Claim(item.Key, item.Value)); } //创建token return CreateToken(claims); } private TnToken CreateToken(List<Claim> claims) { var now = DateTime.Now; var expires = now.Add(TimeSpan.FromMinutes(_options.Value.AccessTokenExpiresMinutes)); var token = new JwtSecurityToken( issuer: _options.Value.Issuer,//Token发布者 audience: _options.Value.Audience,//Token接受者 claims: claims,//携带的负载 notBefore: now,//当前时间token生成时间 expires: expires,//过期时间 signingCredentials: new SigningCredentials(new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_options.Value.IssuerSigningKey)), SecurityAlgorithms.HmacSha256)); return new TnToken { TokenStr = new JwtSecurityTokenHandler().WriteToken(token), Expires = expires }; } /// <summary> /// 验证身份 验证签名的有效性 /// </summary> /// <param name="encodeJwt"></param> /// <param name="validatePayLoad">自定义各类验证; 是否包含那种申明,或者申明的值,例如:payLoad["aud"]?.ToString() == "AXJ"; </param> public bool Validate(string encodeJwt, Func<Dictionary<string, string>, bool> validatePayLoad = null) { var success = true; var jwtArr = encodeJwt.Split('.'); if (jwtArr.Length < 3)//数据格式都不对直接pass { return false; } var header = JsonConvert.DeserializeObject<Dictionary<string, string>>(Base64UrlEncoder.Decode(jwtArr[0])); var payLoad = JsonConvert.DeserializeObject<Dictionary<string, string>>(Base64UrlEncoder.Decode(jwtArr[1])); //配置文件中取出来的签名秘钥 var hs256 = new HMACSHA256(Encoding.ASCII.GetBytes(_options.Value.IssuerSigningKey)); //验证签名是否正确(把用户传递的签名部分取出来和服务器生成的签名匹配即可) success = success && string.Equals(jwtArr[2], Base64UrlEncoder.Encode(hs256.ComputeHash(Encoding.UTF8.GetBytes(string.Concat(jwtArr[0], ".", jwtArr[1]))))); if (!success) { return success;//签名不正确直接返回 } //其次验证是否在有效期内(也应该必须) var now = ToUnixEpochDate(DateTime.UtcNow); success = success && (now >= long.Parse(payLoad["nbf"].ToString()) && now < long.Parse(payLoad["exp"].ToString())); //不需要自定义验证不传或者传递null即可 if (validatePayLoad == null) return true; //再其次 进行自定义的验证 success = success && validatePayLoad(payLoad); return success; } /// <summary> /// 验证身份 验证签名的有效性 /// </summary> /// <param name="encodeJwt"></param> /// <param name="validatePayLoad">自定义各类验证; 是否包含那种申明,或者申明的值,例如:payLoad["aud"]?.ToString() == "AXJ"; </param> public TokenType ValidatePlus(string encodeJwt, Func<Dictionary<string, string>, bool> validatePayLoad, Action<Dictionary<string, string>> action) { var jwtArr = encodeJwt.Split('.'); if (jwtArr.Length < 3)//数据格式都不对直接pass { return TokenType.Fail; } var header = JsonConvert.DeserializeObject<Dictionary<string, string>>(Base64UrlEncoder.Decode(jwtArr[0])); var payLoad = JsonConvert.DeserializeObject<Dictionary<string, string>>(Base64UrlEncoder.Decode(jwtArr[1])); var hs256 = new HMACSHA256(Encoding.ASCII.GetBytes(_options.Value.IssuerSigningKey)); //验证签名是否正确(把用户传递的签名部分取出来和服务器生成的签名匹配即可) if (!string.Equals(jwtArr[2], Base64UrlEncoder.Encode(hs256.ComputeHash(Encoding.UTF8.GetBytes(string.Concat(jwtArr[0], ".", jwtArr[1])))))) { return TokenType.Fail; } //其次验证是否在有效期内(必须验证) var now = ToUnixEpochDate(DateTime.UtcNow); if (!(now >= long.Parse(payLoad["nbf"].ToString()) && now < long.Parse(payLoad["exp"].ToString()))) { return TokenType.Expired; } //不需要自定义验证不传或者传递null即可 if (validatePayLoad == null) { action(payLoad); return TokenType.Ok; } //再其次 进行自定义的验证 if (!validatePayLoad(payLoad)) { return TokenType.Fail; } //可能需要获取jwt摘要里边的数据,封装一下方便使用 action(payLoad); return TokenType.Ok; } private long ToUnixEpochDate(DateTime date) => (long)Math.Round((date.ToUniversalTime() - new DateTimeOffset(1970, 1, 1, 0, 0, 0, TimeSpan.Zero)).TotalSeconds); }
webapi当然在使用验证jwt的时候,都需要提示什么token无效,token已经过期等,我们可以把这种比较公共需要使用的代码提出来,封装一下,方便使用, 避免去手动复制代码,公共的封装一下,然后特殊的情况单独处理即可
补充:还可以添加一个方法,验证的同时直接获取负载部分PayLoad(验证成功了难得再取一次嘛)
/// <summary> /// 验证的同时直接获取负载部分PayLoad(验证成功了难得再取一次嘛) /// </summary> /// <param name="encodeJwt"></param> /// <param name="validatePayLoad"></param> /// <returns></returns> public bool ValidatePayLoad(string encodeJwt,out Dictionary<string, string> outpayLoad, Func<Dictionary<string, string>, bool> validatePayLoad = null) { outpayLoad = null; var success = true; var jwtArr = encodeJwt.Split('.'); if (jwtArr.Length < 3)//数据格式都不对直接pass { return false; } //var header = JsonConvert.DeserializeObject<Dictionary<string, string>>(Base64UrlEncoder.Decode(jwtArr[0])); var payLoad = JsonConvert.DeserializeObject<Dictionary<string, string>>(Base64UrlEncoder.Decode(jwtArr[1])); //配置文件中取出来的签名秘钥 var hs256 = new HMACSHA256(Encoding.ASCII.GetBytes(_options.Value.IssuerSigningKey)); //验证签名是否正确(把用户传递的签名部分取出来和服务器生成的签名匹配即可) success = success && string.Equals(jwtArr[2], Base64UrlEncoder.Encode(hs256.ComputeHash(Encoding.UTF8.GetBytes(string.Concat(jwtArr[0], ".", jwtArr[1]))))); if (!success) { return success;//签名不正确直接返回 } //其次验证是否在有效期内(也应该必须) var now = ToUnixEpochDate(DateTime.UtcNow); success = success && (now >= long.Parse(payLoad["nbf"].ToString()) && now < long.Parse(payLoad["exp"].ToString())); //不需要自定义验证不传或者传递null即可 if (validatePayLoad == null) return true; //再其次 进行自定义的验证 success = success && validatePayLoad(payLoad); outpayLoad = payLoad; return success; }
使用:
Dictionary<string, string> outpayLoad; ITokenHelper tokenHelper = HttpContext.RequestServices.GetService(typeof(ITokenHelper)) as ITokenHelper; //验证jwt bool isValidate = tokenHelper.ValidatePayLoad(token,out outpayLoad, a => a["iss"] == "hello" && a["aud"] == "girl"); if (isValidate==false) { ViewBag.islogin = "false"; HttpContext.Response.Cookies.Delete("token"); return null; }
过滤器实现通用token验证:
http://www.tnblog.net/aojiancc2/article/details/2850