一、自定义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"; }
然后 整体就基本上结束了。虽然说这种方式没有自定义的方式代码容易读,但是也方便了好多。