HTTP Bearer認證及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";
        }

然后 整體就基本上結束了。雖然說這種方式沒有自定義的方式代碼容易讀,但是也方便了好多。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM