一、自定義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"; }
然后 整體就基本上結束了。雖然說這種方式沒有自定義的方式代碼容易讀,但是也方便了好多。