如何使用.NetCore自带类库实现JSON Web Token 认证【三】如何使用通道过滤器验证 JSON Web Token

使用通道过滤器校验:请求是否携带JWT,JWT是否被修改,JWT是否在有效期

目录:

一:什么是JSON Web Token
二:如何获取JSON Web Token
三:如何使用通道过滤器验证 JSON Web Token

Token服务的接口与实例

在Token服务的接口与实例中添加Validate函数用来校验JWT

Token服务

/*-------------------------------------------------------------------------
 * 作者:WuTian
 * 版本号:v1.0
 * 本类主要用途描述及食用方式:
 * JsonWebToken接口
 *  -------------------------------------------------------------------------*/
using EasyCore.Model;

namespace EasyCore.BLL
{
    public interface ITokenService
    {
        /// <summary>
        /// 将一个Model作为额外数据,生成token
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="user"></param>
        /// <returns></returns>
        LoginViewModel CreateToken<T>(T user) where T : class;

        /// <summary>
        /// 创建 TOKEN 无负载数据
        /// </summary>
        /// <returns></returns>
        LoginViewModel CreateToken();


        /// <summary>
        /// 验证身份 验证签名的有效性
        /// </summary>
        /// <param name="encodeJwt"></param>
        void Validate(string encodeJwt);
    }
}

Token实例

namespace EasyCore.BLL
{
    public class TokenService : ITokenService
    {

        #region 依赖注入

        private readonly IOptions<JwtConfigModel> _options;
        public TokenService(IOptions<JwtConfigModel> options)
        {
            _options = options;
        }

        #endregion


        #region 生成Token

        //创建 TOKEN 无负载数据
        public LoginViewModel CreateToken()
        {

            List<Claim> claims = new List<Claim>();

            return CreateToken(claims);

        }


        /// <summary>
        /// 通过反射将一个Model作为负载数据,生成token
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="user"></param>
        /// <returns></returns>
        public LoginViewModel CreateToken<T>(T user) where T : class
        {


            //携带的负载数据,类似一个键值对
            List<Claim> claims = new List<Claim>();

            //填充负载数据的键值对
            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);

        }


        private LoginViewModel CreateToken(List<Claim> claims)
        {
            DateTime now = DateTime.Now;
            DateTime expires = now.Add(TimeSpan.FromMinutes(_options.Value.AccessTokenExpiresMinutes));
            JwtSecurityToken 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 LoginViewModel { TokenStr = new JwtSecurityTokenHandler().WriteToken(token), Expires = expires };
        }
        #endregion

        #region 校验Token
        /// <summary>
        /// 验证身份 验证签名的有效性
        /// </summary>
        /// <param name="encodeJwt"></param>
        public void Validate(string encodeJwt)
        {

            string[] jwtArr = encodeJwt.Split('.');

            Dictionary<string, string> header = JsonConvert.DeserializeObject<Dictionary<string, string>>(Base64UrlEncoder.Decode(jwtArr[0]));
            Dictionary<string, string> payLoad = JsonConvert.DeserializeObject<Dictionary<string, string>>(Base64UrlEncoder.Decode(jwtArr[1]));

            //配置文件中取出来的签名秘钥
            HMACSHA256 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]))))))
            {
                throw new Exception("JsonWebToken不正确");
            }


            //其次验证是否在有效期内
            long now = ToUnixEpochDate(DateTime.UtcNow);

            if (now < long.Parse(payLoad["nbf"].ToString()) || now > long.Parse(payLoad["exp"].ToString()))
            {
                throw new Exception("JsonWebToken不在有效期");
            }


        }



        private long ToUnixEpochDate(DateTime date) =>
            (long)Math.Round((date.ToUniversalTime() - new DateTimeOffset(1970, 1, 1, 0, 0, 0, TimeSpan.Zero)).TotalSeconds);

        #endregion
    }
}

通道过滤器

跳过JWT校验的特性

使用此特性用来忽略JWT校验

/*-------------------------------------------------------------------------
 * 作者:WuTian
 * 版本号:v1.0
 * 本类主要用途及食用方式:
 * 使用此特性用来忽略JWT校验
 *  -------------------------------------------------------------------------*/
using System;

namespace EasyCore.API
{
    public class JsonWebTokenValidateIgnoreAttribute : Attribute
    {

    }
}

用来做JWT校验的特性


namespace EasyCore.API
{
    public class JsonWebTokenValidateAttribute : ActionFilterAttribute
    {

        #region 服务依赖

        private readonly ITokenService tokenService;
        public JsonWebTokenValidateAttribute(ITokenService _tokenService)
        {
            tokenService = _tokenService;
        }
        #endregion

        public override void OnActionExecuting(ActionExecutingContext context)
        {

            //如果有忽略JsonWebToken校验的特性 则不进行校验
            if (context.ActionDescriptor.EndpointMetadata.Any(item => item is JsonWebTokenValidateIgnoreAttribute))
            {
                return;
            }


            string authHeader = context.HttpContext.Request.Headers["Authorization"];//Header中的token


            if (authHeader == null || authHeader.IndexOf("Bearer ") < -1)
            {
                throw new Exception("没获取到正确的JsonWebToken");
            }

            authHeader = authHeader.Replace("Bearer ", "");

            tokenService.Validate(authHeader);


        }

    }
}

控制器

namespace EasyCore.API.Controllers
{
    
    public class LoginController : BaseController
    {

        #region 服务依赖

        private readonly ITokenService tokenService;
        public LoginController(ITokenService _tokenService)
        {
            tokenService = _tokenService;
        }

        #endregion


        /// <summary>
        /// 登录接口 跳过JWT校验 以及获取JWT
        /// </summary>
        /// <param name="paraModel"></param>
        /// <returns></returns>

        [JsonWebTokenValidateIgnore]
        public ActionResult Login(LoginParaModel paraModel)
        {


            //根据用户名和密码去数据库查询,判断用户是否存在,判断密码是否正确。并获取用户ID
            if (paraModel.UserLoginName == "NoUser")
            {
                throw new Exception("当前用户不存在");
            }
            

            //负载数据
            JwtClaimModel claimModel = new JwtClaimModel
            {
                UserId = "10001"
            };

            //负载数据的JWT 
            LoginViewModel viewModel = tokenService.CreateToken(claimModel);


            //返回前端
            return JsonResult(viewModel);

        }


        //不跳过JWT校验
        public ActionResult JwtDemo()
        {
            //返回前端
            return JsonResult("成功");
        }

    }

}

测试

调用Login接口,获取本用户的TOKEN

调用含有JWT校验的业务接口

含有有效TOKEN的调用

无效TOKEN的情况

Token过期的情况

调整系统时间为1天后

posted @ 2021-03-25 10:27  sorachannel  阅读(105)  评论(0)    收藏  举报