NET CORE项目中JWT的使用

一、自定义CheckJWTAttribute特性方式

之前使用的是这种方式,根据jwt原理自定义生成JWT、验证jwt,感觉挺好。原理就是自定义一个拦截器(特性),拦截器对每个请求都优先进行处理,认证成功的进行下一步操作。

1、定义JWTPayload类

using System;
namespace HyDataMiddleground.Util
{
    public class JWTPayload
    {
        public string UserName { get; set; }
        public string Email { get; set; }
        public string UserId { get; set; }
        public DateTime Expire { get; set; }
    }
}

 

2、定义CheckJWTAttribute特性

用于验证jwt:

using Microsoft.AspNetCore.Mvc.Filters;
using System;
using System.Threading.Tasks;

namespace HyDataMiddleground.Util
{
    /// <summary>
    /// JWT校检
    /// </summary>
    public class CheckJWTAttribute : BaseActionFilterAsync
    {
        private static readonly int _errorCode = 401;
        public override async Task OnActionExecuting(ActionExecutingContext context)
        {
            if (context.ContainsFilter<NoCheckJWTAttribute>()) return;
            try
            {
                var req = context.HttpContext.Request;
                string token = req.GetToken();
                if (token.IsNullOrEmpty())
                {
                    context.Result = Error("缺少token", _errorCode);
                    return;
                }
                if (!JWTHelper.CheckToken(token, JWTHelper.JWTSecret))
                {
                    context.Result = Error("token校检失败!", _errorCode);
                    return;
                }

                var payload = JWTHelper.GetPayload<JWTPayload>(token);
                if (payload.Expire < DateTime.Now)
                {
                    context.Result = Error("token过期!", _errorCode);
                    return;
                }
            }
            catch (Exception ex)
            {
                context.Result = Error(ex.Message, _errorCode);
            }
            await Task.CompletedTask;
        }
    }
}

3、定义其他帮助类

(1)BaseActionFilterAsync类

using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using System;
using System.Threading.Tasks;

namespace HyDataMiddleground.Util
{
    public class BaseActionFilterAsync : Attribute, IAsyncActionFilter
    {
        /// <summary>
        /// action执行之前执行
        /// </summary>
        /// <param name="context"></param>
        /// <returns></returns>
        public async virtual Task OnActionExecuting(ActionExecutingContext context)
        {
            await Task.CompletedTask;
        }
        /// <summary>
        /// action执行之后执行
        /// </summary>
        /// <param name="context"></param>
        /// <returns></returns>
        public async virtual Task OnActionExecuted(ActionExecutedContext context)
        {
            await Task.CompletedTask;
        }
        /// <summary>
        /// 在模型绑定完成后,在操作之前异步调用。
        /// </summary>
        /// <param name="context"></param>
        /// <param name="next"></param>
        /// <returns></returns>
        public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
        {
            await OnActionExecuting(context);
            if (context.Result == null)
            {
                var nextContext = await next();
                await OnActionExecuted(nextContext);
            }
        }

        /// <summary>
        /// 返回JSON
        /// </summary>
        /// <param name="json">json字符串</param>
        /// <returns></returns>
        public ContentResult JsonContent(string json)
        {
            return new ContentResult { Content = json, StatusCode = 200, ContentType = "application/json; charset=utf-8" };
        }

        /// <summary>
        /// 返回成功
        /// </summary>
        /// <returns></returns>
        public ContentResult Success()
        {
            AjaxResult res = new AjaxResult
            {
                Success = true,
                Msg = "请求成功!"
            };

            return JsonContent(res.ToJson());
        }

        /// <summary>
        /// 返回成功
        /// </summary>
        /// <param name="msg">消息</param>
        /// <returns></returns>
        public ContentResult Success(string msg)
        {
            AjaxResult res = new AjaxResult
            {
                Success = true,
                Msg = msg
            };

            return JsonContent(res.ToJson());
        }

        /// <summary>
        /// 返回成功
        /// </summary>
        /// <param name="data">返回的数据</param>
        /// <returns></returns>
        public ContentResult Success<T>(T data)
        {
            AjaxResult<T> res = new AjaxResult<T>
            {
                Success = true,
                Msg = "请求成功!",
                Data = data
            };

            return JsonContent(res.ToJson());
        }

        /// <summary>
        /// 返回错误
        /// </summary>
        /// <returns></returns>
        public ContentResult Error()
        {
            AjaxResult res = new AjaxResult
            {
                Success = false,
                Msg = "请求失败!"
            };

            return JsonContent(res.ToJson());
        }

        /// <summary>
        /// 返回错误
        /// </summary>
        /// <param name="msg">错误提示</param>
        /// <returns></returns>
        public ContentResult Error(string msg)
        {
            AjaxResult res = new AjaxResult
            {
                Success = false,
                Msg = msg,
            };

            return JsonContent(res.ToJson());
        }

        /// <summary>
        /// 返回错误
        /// </summary>
        /// <param name="msg">错误提示</param>
        /// <param name="errorCode">错误代码</param>
        /// <returns></returns>
        public ContentResult Error(string msg, int errorCode)
        {
            AjaxResult res = new AjaxResult
            {
                Success = false,
                Msg = msg,
                ErrorCode = errorCode
            };

            return JsonContent(res.ToJson());
        }
    }
}

(2)AjaxResult类

namespace HyDataMiddleground.Util
{
    /// <summary>
    /// Ajax请求结果
    /// </summary>
    public class AjaxResult
    {
        /// <summary>
        /// 是否成功
        /// </summary>
        public bool Success { get; set; } = true;

        /// <summary>
        /// 错误代码
        /// </summary>
        public int ErrorCode { get; set; }

        /// <summary>
        /// 返回消息
        /// </summary>
        public string Msg { get; set; }
    }
}

(3)上述代码中使用到的扩展类Extention

using Newtonsoft.Json;
using System;
using System.ComponentModel;
using System.IO;
using System.Reflection;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;

namespace HyDataMiddleground.Util
{
    public static partial class Extention
    {
        /// <summary>
        /// 构造函数
        /// </summary>
        static Extention()
        {
            JsonSerializerSettings setting = new JsonSerializerSettings();
            JsonConvert.DefaultSettings = new Func<JsonSerializerSettings>(() =>
            {
                //日期类型默认格式化处理
                setting.DateFormatHandling = DateFormatHandling.MicrosoftDateFormat;
                setting.DateFormatString = "yyyy-MM-dd HH:mm:ss";
                return setting;
            });
        }
/// <summary> /// 将对象序列化成Json字符串 /// </summary> /// <param name="obj">需要序列化的对象</param> /// <returns></returns> public static string ToJson(this object obj) { return JsonConvert.SerializeObject(obj); } } }

(4)JWTHelper类

using Newtonsoft.Json.Linq;

namespace HyDataMiddleground.Util
{
    /// <summary>
    /// JWT帮助类
    /// </summary>
    public class JWTHelper
    {
        private static readonly string _headerBase64Url = "{\"alg\":\"HS256\",\"typ\":\"JWT\"}".Base64UrlEncode();
        public static readonly string JWTSecret = ConfigHelper.GetValue("JWTSecret");

        /// <summary>
        /// 生成Token
        /// </summary>
        /// <param name="payloadJsonStr">载荷,数据JSON字符串</param>
        /// <param name="secret">秘钥</param>
        /// <returns></returns>
        public static string GetToken(string payloadJsonStr, string secret)
        {
            string payloadBase64Url = payloadJsonStr.Base64UrlEncode();
            string sign = $"{_headerBase64Url}.{payloadBase64Url}".ToHMACSHA256String(secret);
            return $"{_headerBase64Url}.{payloadBase64Url}.{sign}";
        }

        /// <summary>
        /// 获取Token中的数据
        /// </summary>
        /// <param name="token"></param>
        /// <returns></returns>
        public static JObject GetPayload(string token)
        {
            return token.Split(.)[1].Base64UrlDecode().ToJObject();
        }

        /// <summary>
        /// 获取Token中的数据
        /// </summary>
        /// <typeparam name="T">泛型</typeparam>
        /// <param name="token">token</param>
        /// <returns></returns>
        public static T GetPayload<T>(string token)
        {
            if (token.IsNullOrEmpty())
                return default;

            return token.Split(.)[1].Base64UrlDecode().ToObject<T>();
        }

        /// <summary>
        /// 校验Token
        /// </summary>
        /// <param name="token">token</param>
        /// <param name="secret">密钥</param>
        /// <returns></returns>
        public static bool CheckToken(string token, string secret)
        {
            var items = token.Split(.);
            var oldSign = items[2];
            string newSign = $"{items[0]}.{items[1]}".ToHMACSHA256String(secret);
            return oldSign == newSign;
        }
    }
}

3、定义NoCheckJWTAttribute类

namespace HyDataMiddleground.Util
{
    /// <summary>
    /// 忽略JWT校验
    /// </summary>
    public class NoCheckJWTAttribute : BaseActionFilterAsync
    {
    }
}

4、使用CheckJWTAttribute

(1)定义一个BaseApiController,所有的controller都继承该类,BaseApiController类使用CheckJWTAttribute特性

using HyDataMiddleground.Util;
using Microsoft.AspNetCore.Mvc;

namespace HyDataMiddleground.Admin
{
    /// <summary>
    /// Mvc对外接口基控制器
    /// </summary>
    [CheckJWT]
    public class BaseApiController : BaseController
    {
    }
}

(2)不需要认证的,使用NoCheckJWT限制,如下:

     /// <summary>
        /// 用户登录
        /// </summary>
        /// <param name="input">LoginInputDTO实体参数</param>
        /// <returns></returns>
        [Produces("application/json")]
        [HttpPost]
        [NoCheckJWT]
        public async Task<string> SubmitLogin(LoginInputDTO input)
        {
            return await _userBus.SubmitLoginAsync(input);
        }

二、使用aspnetcore提供的组件

1、安装组件

通过nugut搜索安装Microsoft.AspNetCore.Authentication.JwtBearer 

2、jwtconfig配置

{  
  "Jwt": {
    "Issuer": "Issuer",
    "Audience": "Audience",
    "SigningKey": "EF1DA5B4-C7FA-4240-B997-7D1701BF9BE2"
  }  
}

3、定义jwtconfig对应的实体

public class JwtConfig
{
    public string Issuer{get;set;}  
    public string Audience{get;set;}
    public string SigningKey{get;set;}  
}

4、Startup.cs 配置

(1)ConfigureServices 中需要进行添加的信息

#region   Token验证信息   JWT
//读取JWT的配置信息
var jwtconfig = Configuration.GetSection("Jwt").Get<JwtConfig>();
//JWT身份认证
services.AddAuthentication(option =>
{
    option.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
    option.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(option =>
{
    option.TokenValidationParameters = new TokenValidationParameters
    {
        ValidIssuer = jwtconfig.Issuer,
        ValidAudience = jwtconfig.Audience,
        ValidateIssuer = true,
        ValidateLifetime = true,
        IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtconfig.SigningKey)),
        // 缓冲过期时间,总的有效时间等于这个时间加上jwt的过期时间,如果不配置,默认是5分钟
        ClockSkew = TimeSpan.FromSeconds(5)
    };

    option.Events = new JwtBearerEvents
    {
        //此处为权限验证失败后触发的事件
        OnChallenge = context =>
        {
            //此处代码为终止.Net Core默认的返回类型和数据结果,这个很重要哦,必须
            context.HandleResponse();
            //自定义自己想要返回的数据结果,我这里要返回的是Json对象,通过引用Newtonsoft.Json库进行转换
            var payload = JsonConvert.SerializeObject(new { message = "授权未通过,Token无效", status = false, code = 401 });
            //自定义返回的数据类型
            context.Response.ContentType = "application/json";
            //自定义返回状态码,默认为401 我这里改成 200
            context.Response.StatusCode = StatusCodes.Status200OK;
            //输出Json数据结果
            context.Response.WriteAsync(payload);
            return Task.FromResult(0);
         }
    };
});

services.AddOptions().Configure<JwtConfig>(Configuration.GetSection("Jwt"));
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);                            
#endregion

(2)Configure需要添加的信息

// JWT身份认证
app.UseAuthentication();
app.UseAuthorization();

以上配置好了 就基本上可以使用JWT验证了,下面介绍如何生成jwt token

5、封装了一个帮助类

    /// <summary>
    /// JWT帮助类信息
    /// </summary>
    public class JwtHelper
    {
        /// <summary>
        /// 颁发JWT字符串
        /// </summary>
        /// <param name="tokenModel"></param>
        /// <returns></returns>
        public static string IssueJwt(Claim[] claim)
        {
            // 读取对应的配置信息
            string iss = ConfigHelper.GetSectionValue("Jwt:Issuer");
            string aud = ConfigHelper.GetSectionValue("Jwt:Audience");
            string secret = ConfigHelper.GetSectionValue("Jwt:SigningKey");
            //加密关键字
            var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(secret));
            //编码格式
            var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
            //token存储相关信息
            var token = new JwtSecurityToken(
                issuer: iss,
                audience: aud,
                claims: claim,
                notBefore: DateTime.Now,
                expires: DateTime.Now.AddSeconds(300),
                signingCredentials: creds);
            var jwtHandler = new JwtSecurityTokenHandler();
            //生成对应的编码信息
            var encodedJwt = jwtHandler.WriteToken(token);
            return encodedJwt;
        }
        /// <summary>
        /// 解析
        /// </summary>
        /// <param name="jwtStr"></param>
        /// <returns></returns>
        public static List<Claim> SerializeJwt(string jwtStr)
        {
            var jwtHandler = new JwtSecurityTokenHandler();
            JwtSecurityToken jwtToken = jwtHandler.ReadJwtToken(jwtStr);
            object role;
            try
            {
                jwtToken.Payload.TryGetValue(ClaimTypes.Role, out role);
            }
            catch (Exception e)
            {
                Console.WriteLine(e);
                throw;
            }
            var list = jwtToken.Claims.ToList();
            return list;
        }
    }

6、控制器层使用示例

        /// <summary>
        /// 测试获取token信息
        /// </summary>
        /// <returns></returns>
        [HttpGet]
        public ActionResult<string> Get()
        {           
            //参数存储的信息
            var claim = new Claim[]{
            new Claim("UserName", "测试"),
            new Claim("UserId", "10086")
        };
            //生成证书
            var token = JwtHelper.IssueJwt(claim);
            //解析证书
            var data=  JwtHelper.SerializeJwt(token);
            return Ok(new { token = token, data= data });
        }
        /// <summary>
        /// 在需要身份认证的方法添加[Authorize]
        /// </summary>
        [Authorize]
        [HttpGet("{id}")]
        public ActionResult<string> Get(int id)
        {
            return "value";
        }

然后 整体就基本上结束了。虽然说这种方式没有自定义的方式代码容易读,但是也方便了好多。

NET CORE项目中JWT的使用

上一篇:通过 jQuery添加新元素和内容。


下一篇:PHP装饰器模式