


上一篇说了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
欢迎加群讨论技术,1群:677373950(满了,可以加,但通过不了),2群:656732739。有需要软件开发,或者学习软件技术的朋友可以和我联系~(Q:815170684)
雨雨雨雨辰
这个很强