剑轩

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





关于TNBLOG
TNBLOG,技术分享
ICP备案 :渝ICP备18016597号-1
App store Android
精彩评论
{{item.replyName}}
{{item.content}}
{{item.time}}
{{subpj.replyName}}
@{{subpj.beReplyName}}{{subpj.content}}
{{subpj.time}}
猜你喜欢