50010702506256
分类:
.NET Core
上一篇说了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)