Jwt的自定義使用 中間件+身份驗證鑒權方式


 

本文實例環境及版本 NetCore 3.1

一、JWT簡介

JWT其全稱是JSON Web Token,官網地址:https://jwt.io/

通俗地說,JWT的本質就是一個字符串,它是將用戶信息保存到一個Json字符串中,然后進行編碼后得到一個JWT token,並且這個JWT token帶有簽名信息,接收后可以校驗是否被篡改,所以可以用於在各方之間安全地將信息作為Json對象傳輸。

 

JWT認證的優勢
對比傳統的session認證方式,JWT的優勢是:

1、JWT Token數據量小,傳輸速度也很快。因為JWT Token是以JSON加密形式保存在客戶端的,所以JWT是跨語言的,原則上任何web形式都支持

2、不需要在服務端保存會話信息,也就是說不依賴於cookie和session,所以沒有了傳統session認證的弊端,特別適用於分布式微服務

3、單點登錄友好 使用Session進行身份認證的話,由於cookie無法跨域,難以實現單點登錄。但是,使用token進行認證的話, token可以被保存在客戶端的任意位置的內存中,不一定是cookie,所以不依賴cookie,不會存在這些問題
適合移動端應用 使用Session進行身份認證的話,需要保存一份信息在服務器端,而且這種方式會依賴到Cookie(需要 Cookie 保存 SessionId),所以不適合移動端


二、具體使用

1、NuGet導入Microsoft.AspNetCore.Authentication、Microsoft.AspNetCore.Authentication.JwtBearer、Microsoft.IdentityModel.Tokens

2、定義一個認證用戶信息實體類如下

    /// <summary>
    /// 用戶信息model
    /// </summary>
    public class UserInfo
    {
        /// <summary>
        /// 用戶編號
        /// </summary>
        public string userId { get; set; }
        /// <summary>
        /// 用戶名稱
        /// </summary>
        public string userName { get; set; }
        /// <summary>
        /// 角色、標識
        /// </summary>
        public string roleType { get; set; }/// <summary>
        /// Token的過期時間
        /// </summary>
        public DateTime expiresDate { get; set; }
    }

3、Jwt配置對象

    /// <summary>
    /// jwt配置對象
    /// </summary>
    public class JWTTokenOptions
    {
        /// <summary>
        /// Jwt認證Key
        /// </summary>
        public string SecurityKey { get; set; }
        /// <summary>
        /// 過期時間 單位為小時
        /// </summary>
        public int Expire { get; set; }
        /// <summary>
        /// 觀眾 相當於接受者受眾者
        /// </summary>
        public string Audience { get; set; }
        /// <summary>
        /// 發行者
        /// </summary>
        public string Issuer { get; set; }
    }

4、配置文件appsettings.json 添加如下

 "JWTTokenOptions": {
    "Audience": "http://localhost:5200",
    "Issuer": "http://localhost:5200",
    "SecurityKey": "**************************", //用於生成token的秘鑰 至少16位此處24位
    "Expire": "7" //過期時間 單位為天
  }

5、Jwt管理接口

    /// <summary>
    /// 用於生成Token的接口
    /// </summary>
    public interface IAuthManage
    {
        /// <summary>
        /// 生成JwtToken
        /// </summary>
        /// <param name="user">用戶信息</param>
        /// <returns></returns>
        string GenerateJwtToken(UserInfo user);
    }

6、接口實現類

    /// <summary>
    /// 用於生成token的實現類
    /// </summary>
    public class MicrosoftJwtAuthManage : IAuthManage
    {
        private readonly JWTTokenOptions _authOptions;
        public MicrosoftJwtAuthManage(JWTTokenOptions authOptions)
        {
            _authOptions = authOptions;
        }

        public string GenerateJwtToken(UserInfo user)
        {
            var days = _authOptions.Expire.ToString();

            var tokenHandler = new JwtSecurityTokenHandler();
            var key = Encoding.ASCII.GetBytes(_authOptions.SecurityKey); //獲取用於生成Token的key
            var tokenDescriptor = new SecurityTokenDescriptor
            {
                Subject = new ClaimsIdentity(new Claim[]
                {
                    new Claim("userInfo",user.ToJson())  //將實體類轉json存入Token中
                }),
                Expires = DateTime.UtcNow.AddDays(_authOptions.Expire),//過期時間
                SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
            };
            var token = tokenHandler.CreateToken(tokenDescriptor);
            return tokenHandler.WriteToken(token);
        }
    }

7、驗證Token的中間件

    /// <summary>
    /// JWT中間件 用於驗證Token信息的
    /// </summary>
    public class JwtMiddleware
    {
        private readonly RequestDelegate _next;
        private readonly JWTTokenOptions _authOptions;

        public JwtMiddleware(RequestDelegate next, JWTTokenOptions authOptions)
        {
            _next = next;
            _authOptions = authOptions;
        }

        public async Task Invoke(HttpContext context)
        {
            //獲取傳遞過來的Token,可自定義擴展
            var token = context.Request.Headers["Authorization"].FirstOrDefault()?.Split(" ").Last()
                        ?? context.Request.Headers["Token"].FirstOrDefault()
                        ?? context.Request.Query["Token"].FirstOrDefault()
                        ?? context.Request.Cookies["Token"];

            if (token != null)
                AttachUserToContext(context, token);

            await _next(context);
        }

        private void AttachUserToContext(HttpContext context, string token)
        {
            try
            {
                var tokenHandler = new JwtSecurityTokenHandler();
                var key = Encoding.UTF8.GetBytes(_authOptions.SecurityKey); //獲取到用於生成token的加密key
                tokenHandler.ValidateToken(token, new TokenValidationParameters
                {
                    ValidateIssuerSigningKey = true, //是否驗證Key SecurityKey
                    IssuerSigningKey = new SymmetricSecurityKey(key), //拿到SecurityKey
                    ValidateIssuer = false, //是否驗證Issuer
                    ValidateAudience = false, //是否驗證Audience
                    ValidateLifetime = true, //是否驗證Token的失效時間
                }, out SecurityToken validatedToken);

                var jwtToken = (JwtSecurityToken)validatedToken;
                var user = jwtToken.Claims.First(x => x.Type == "userInfo").Value; //獲取Token中存入的用戶信息
                var userModel = Newtonsoft.Json.JsonConvert.DeserializeObject<UserInfo>(user);


                //寫入認證信息,方便業務類使用
                var claimsIdentity = new ClaimsIdentity(new Claim[] { new Claim("userInfo", jwtToken.Claims.First(x => x.Type == "userInfo").Value) });
                Thread.CurrentPrincipal = new ClaimsPrincipal(claimsIdentity);

                //將Token中解析出來的用戶信息存入當前請求中
                context.Items["UserModel"] = userModel;
            }
            catch(Exception ex)
            {
                context.Items["UserModel"] = null;
            }
        }

    }

8、全局權限認證過濾器

    /// <summary>
    /// 鑒權,身份驗證授權的過濾器
    /// </summary>
    public class ApiAuthorizeAttribute : IAuthorizationFilter 
    {
        public void OnAuthorization(AuthorizationFilterContext context)
        {
            var user = context.HttpContext.Items["UserModel"];

            //context.HttpContext.Response.WriteAsync("我是身份驗證過濾器");

            //驗證是否需要授權和授權信息
            if (HasAllowAnonymous(context) == false && user == null)
            {
                context.Result = new JsonResult(new { code = StatusCodes.Status401Unauthorized, message = "Token驗證失敗或已過期請重新登錄並獲取!" });
                //也可自定義返回的HTTP code 
                //{ StatusCode = StatusCodes.Status401Unauthorized };
            }
        }

        private static bool HasAllowAnonymous(AuthorizationFilterContext context)
        {
            var filters = context.Filters;
            if (filters.OfType<IAllowAnonymousFilter>().Any())
            {
                return true;
            }
            var endpoint = context.HttpContext.GetEndpoint();
            return endpoint?.Metadata?.GetMetadata<IAllowAnonymous>() != null;
        }
    }

9、Startup里 ConfigureServices 添加

    #region JWT配置
    //讀取配置文件中的JWTTokenOptions字段
    services.Configure<JWTTokenOptions>(this.Configuration.GetSection("JWTTokenOptions"));

    //將字段中的值賦值給 JWTTokenOptions 實體類
    JWTTokenOptions tokenOptions = new JWTTokenOptions();
    Configuration.Bind("JWTTokenOptions", tokenOptions);
    //依賴注入 將配置注入
    services.AddSingleton(tokenOptions);
    services.AddSingleton<IAuthManage>(new MicrosoftJwtAuthManage(tokenOptions));
    #endregion

10、Startup里 Configure 添加

 //啟用jwt認證中間件
 app.UseMiddleware<JwtMiddleware>();

11、生成Token

public class LoginController : Controller
    {
        private IAuthManage _AuthManage = null;
        /// <summary>
        /// 用於獲取jwt配置
        /// </summary>
        private readonly JWTTokenOptions _JWTTokenOptions;

        public LoginController(IAuthManage AuthManage, IOptionsMonitor<JWTTokenOptions> jwtTokenOptions)
        {
            _AuthManage = AuthManage;
            _JWTTokenOptions = jwtTokenOptions.CurrentValue;
        }
        /// <summary>
        /// 登錄操作
        /// </summary>
        /// <returns></returns>
        [HttpGet("Login")]
        public IActionResult Index()
        {
            UserInfo user = new UserInfo();
            user.userId = "55114d44-a2d6-4f1a-a749-0618ca542591";
            user.userName = "張三";
            user.roleType = "sys_p_user";//讀取配置的過期時間 單位為天
            double expire = Convert.ToDouble(_JWTTokenOptions.Expire);
            var expireTime = DateTime.Now.AddDays(expire); //Token的過期日期
            user.expiresDate = expireTime;//生成Token 
            string token = this._AuthManage.GenerateJwtToken(user);
            
            var result = new
            {
                Result = true,
                Token= token 
};

return Ok(result);
}

 獲取存入請求中的UserInfo信息

  var userInfo= (UserInfo)this.HttpContext.Items["UserModel"];

 

如果個別接口不需要認證,可以使用 [AllowAnonymous] 特性,可以放在接口控制器或方法上都可以。

總結

1、JwtMiddleware 類文件為驗證Token的中間件,如果驗證通過把token中的信息存入上下文中
2、ApiAuthorizeAttribute 類為鑒權的過濾器,查看當前上下文中是否有存入的token信息,如果沒有則返回自定義的消息
3、在Startup中無需配置太多東西,只需要注入JwtMiddleware中間件、注入JWTTokenOptions(jwt的配置)、添加一個全局的鑒權認證過濾器(ApiAuthorizeAttribute)即可
使用方便可且可以自由定義和擴展!

 

才疏學淺,相關文檔等僅供自我總結,如有相關問題可留言交流謝謝。

 


免責聲明!

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



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