從壹開始前后端分離【 .NET Core2.0 +Vue2.0 】框架之五 || Swagger的使用 3.3 JWT權限驗證【修改】


重大更新:

這篇文章還有一個補充篇,請看完這篇后,再看這個《從壹開始前后端分離[.netCore 不定期 ] 36 ║解決JWT權限驗證過期問題》,

兩個是上下銜接的,主要是解決本文中,過期時間無效的問題。

 

群友反饋:

群里有小伙伴反饋,在Swagger使用的時候報錯,無法看到列表,這里我說下如何調試和主要問題:

1、如果遇到問題,這樣的:

 

請在瀏覽器 =》 F12 ==》 console 控制台 ==》點擊錯誤信息地址

或者直接鏈接http://localhost:xxxxx/swagger/v1/swagger.json,就能看到錯誤了

 

 

 

更新:

1、目前已經實現多用戶對接口的訪問,也可以通過將用戶的信息自定義存入到 Token 中,然后反序列化后,將該用戶的多個角色一起添加到 clain 中,實現一個用戶的多角色訪問。下文中包含以下字樣的地方:

注意這個可以添加多個角色聲明,請注意這是一個 list

2、網友@rookie丶    Jwt token時效不起作用,有知道的小伙伴,可以回答下。

 

 


提要

書接上文,在前邊的兩篇文章中,我們簡單提到了接口文檔神器Swagger,《從零開始搭建自己的前后端分離【 .NET Core2.0 Api + Vue 2.0 + AOP + 分布式】框架之三 || Swagger的使用 3.1》、《從零開始搭建自己的前后端分離【 .NET Core2.0 Api + Vue 2.0 + AOP + 分布式】框架之四 || Swagger的使用 3.2》,兩個文章中,也對常見的幾個問題做了簡單的討論,最后還剩下一個小問題,

如何給接口實現權限驗證?

其實關於這一塊,我思考了下,因為畢竟我的項目中是使用的vue + api 搭建一個前台展示,大部分頁面都沒有涉及到權限驗證,本來要忽略這一章節,可是猶豫再三,還是給大家簡單分析了下,個人還是希望陪大家一直搭建一個較為強大的,只要是涉及到后端那一定就需要 登陸=》驗證了,本文主要是參考網友https://www.cnblogs.com/RayWang/p/9255093.html的思路,我自己稍加改動,大家都可以看看。

根據維基百科定義,JWT(讀作 [/dʒɒt/]),即JSON Web Tokens,是一種基於JSON的、用於在網絡上聲明某種主張的令牌(token)。JWT通常由三部分組成: 頭信息(header), 消息體(payload)和簽名(signature)。它是一種用於雙方之間傳遞安全信息的表述性聲明規范。JWT作為一個開放的標准(RFC 7519),定義了一種簡潔的、自包含的方法,從而使通信雙方實現以JSON對象的形式安全的傳遞信息。

以上是JWT的官方解釋,可以看出JWT並不是一種只能權限驗證的工具,而是一種標准化的數據傳輸規范。所以,只要是在系統之間需要傳輸簡短但卻需要一定安全等級的數據時,都可以使用JWT規范來傳輸。規范是不因平台而受限制的,這也是JWT做為授權驗證可以跨平台的原因。

如果理解還是有困難的話,我們可以拿JWT和JSON類比:

JSON是一種輕量級的數據交換格式,是一種數據層次結構規范。它並不是只用來給接口傳遞數據的工具,只要有層級結構的數據都可以使用JSON來存儲和表示。當然,JSON也是跨平台的,不管是Win還是Linux,.NET還是Java,都可以使用它作為數據傳輸形式。

1)客戶端向授權服務系統發起請求,申請獲取“令牌”。

2)授權服務根據用戶身份,生成一張專屬“令牌”,並將該“令牌”以JWT規范返回給客戶端

3)客戶端將獲取到的“令牌”放到http請求的headers中后,向主服務系統發起請求。主服務系統收到請求后會從headers中獲取“令牌”,並從“令牌”中解析出該用戶的身份權限,然后做出相應的處理(同意或拒絕返回資源)


 

一、通過Jwt獲取Token,並通過緩存記錄,配合中間件實現驗證

在之前的搭建中,swagger已經基本成型,其實其功能之多,不是我這三篇所能寫完的,想要添加權限,先從服務開始

 

在ConfigureServices中,增加以下代碼

#region Token綁定到ConfigureServices 

 //添加header驗證信息 

 var security = new Dictionary> { { "Blog.Core", new string[] { } }, };

                c.AddSecurityRequirement(security);

                //方案名稱“Blog.Core”可自定義,上下一致即可

                c.AddSecurityDefinition("Blog.Core", new ApiKeyScheme

                {

                    Description = "JWT授權(數據將在請求頭中進行傳輸) 直接在下框中輸入{token}\"",

                    Name = "Authorization",//jwt默認的參數名稱

                    In = "header",//jwt默認存放Authorization信息的位置(請求頭中)

                    Type = "apiKey"

                });

                #endregion
View Code

 最終的是這樣的

/// <summary>
        /// ConfigureServices 方法
        /// </summary>
        /// <param name="services"></param>
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc();

            #region Swagger
            services.AddSwaggerGen(c =>
            {
                c.SwaggerDoc("v1", new Info
                {
                    Version = "v0.1.0",
                    Title = "Blog.Core API",
                    Description = "框架說明文檔",
                    TermsOfService = "None",
                    Contact = new Swashbuckle.AspNetCore.Swagger.Contact { Name = "Blog.Core", Email = "Blog.Core@xxx.com", Url = "https://www.jianshu.com/u/94102b59cc2a" }
                });

                //就是這里

                #region 讀取xml信息
                var basePath = PlatformServices.Default.Application.ApplicationBasePath;
                var xmlPath = Path.Combine(basePath, "Blog.Core.xml");//這個就是剛剛配置的xml文件名
                var xmlModelPath = Path.Combine(basePath, "Blog.Core.Model.xml");//這個就是Model層的xml文件名
                c.IncludeXmlComments(xmlPath, true);//默認的第二個參數是false,這個是controller的注釋,記得修改
                c.IncludeXmlComments(xmlModelPath);
                #endregion

                #region Token綁定到ConfigureServices
                //添加header驗證信息
                //c.OperationFilter<SwaggerHeader>();
                var security = new Dictionary<string, IEnumerable<string>> { { "Blog.Core", new string[] { } }, };
                c.AddSecurityRequirement(security);
                //方案名稱“Blog.Core”可自定義,上下一致即可
                c.AddSecurityDefinition("Blog.Core", new ApiKeyScheme
                {
                    Description = "JWT授權(數據將在請求頭中進行傳輸) 直接在下框中輸入{token}\"",
                    Name = "Authorization",//jwt默認的參數名稱
                    In = "header",//jwt默認存放Authorization信息的位置(請求頭中)
                    Type = "apiKey"
                }); 
                #endregion


            });
            #endregion

            #region Token服務注冊
            services.AddSingleton<IMemoryCache>(factory =>
             {
                 var cache = new MemoryCache(new MemoryCacheOptions());
                 return cache;
             });
            services.AddAuthorization(options =>
            {
                options.AddPolicy("Client", policy => policy.RequireRole("Client").Build());
                options.AddPolicy("Admin", policy => policy.RequireRole("Admin").Build());
                options.AddPolicy("AdminOrClient", policy => policy.RequireRole("Admin,Client").Build());
            });
            #endregion
        }

 

然后執行代碼,就可以看到效果

 


圖 1

圖 2

它的作用就是,每次請求時,從Header報文中,獲取密鑰token,這里根據token可以進一步判斷相應的權限等。

重要更新:

最新的Github 開源代碼中,已經做了調整,需要在輸入 Token的時候,前邊加上 Bearer ,比如是這樣的:

 

 

接下來,就是在項目中配置5個地方(感謝 @貓出沒 提出歧義,其實不是我們要新建五個文件,而是5個地方,1.JwtHelper.cs(幫助類) + 2.JwtTokenAuth.cs(執行中間件) + 3.一個Token類(存放我們的token信息) + 4.在startup中的configure方法配置啟動中間件 + 5. 添加 controller 特性),如下圖

 圖 3

 

具體來說:

1:JwtTokenAuth,一個中間件,用來過濾每一個http請求,就是每當一個用戶發送請求的時候,都先走這一步,然后再去訪問http請求的接口

     public Task Invoke(HttpContext httpContext)
        {
            //檢測是否包含'Authorization'請求頭
            if (!httpContext.Request.Headers.ContainsKey("Authorization"))
            {
                return _next(httpContext);
            }
            var tokenHeader = httpContext.Request.Headers["Authorization"].ToString();

            TokenModelJWT tm = JwtHelper.SerializeJWT(tokenHeader);//序列化token,獲取授權

            //授權 注意這個可以添加多個角色聲明,請注意這是一個 list
            var claimList = new List<Claim>(); 
            var claim = new Claim(ClaimTypes.Role, tm.Role);
            claimList.Add(claim);
            var identity = new ClaimsIdentity(claimList);
            var principal = new ClaimsPrincipal(identity);
            httpContext.User = principal;

            return _next(httpContext);
        }

2:JwtHelper 一個幫助類,可以生成Token,和講Token反序列成model

public class JwtHelper
    {
        public static string secretKey { get; set; } = "sdfsdfsrty45634kkhllghtdgdfss345t678fs";
        /// <summary>
        /// 頒發JWT字符串
        /// </summary>
        /// <param name="tokenModel"></param>
        /// <returns></returns>
        public static string IssueJWT(TokenModelJWT tokenModel)
        {
            var dateTime = DateTime.UtcNow;
            var claims = new Claim[]
            {
                new Claim(JwtRegisteredClaimNames.Jti,tokenModel.Uid.ToString()),//Id
                new Claim("Role", tokenModel.Role),//角色
                new Claim(JwtRegisteredClaimNames.Iat,dateTime.ToString(),ClaimValueTypes.Integer64)
            };
            //秘鑰
            var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(JwtHelper.secretKey));
            var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);

            var jwt = new JwtSecurityToken(
                issuer: "Blog.Core",
                claims: claims, //聲明集合
                expires: dateTime.AddHours(2),
                signingCredentials: creds);

            var jwtHandler = new JwtSecurityTokenHandler();
            var encodedJwt = jwtHandler.WriteToken(jwt);

            return encodedJwt;
        }

        /// <summary>
        /// 解析
        /// </summary>
        /// <param name="jwtStr"></param>
        /// <returns></returns>
        public static TokenModelJWT SerializeJWT(string jwtStr)
        {
            var jwtHandler = new JwtSecurityTokenHandler();
            JwtSecurityToken jwtToken = jwtHandler.ReadJwtToken(jwtStr);
            object role = new object(); ;
            try
            {
                jwtToken.Payload.TryGetValue("Role", out role);
            }
            catch (Exception e)
            {
                Console.WriteLine(e);
                throw;
            }
            var tm = new TokenModelJWT
            {
                Uid = (jwtToken.Id).ObjToInt(),
                Role = role != null ? role.ObjToString() : "",
            };
            return tm;
        }
    }

 注意:

ObjToInt() 和 ObjToString()//這是我封裝的方法,在Blog.Core.Model層的UtilConvert類中,注意命名控制是整個解決方案的,這樣全局就可以使用

  

3:定義中的TokenModelJWT,是一個令牌類,主要存儲個人角色信息,自己簡單寫一個,也可以是你登陸的時候的用戶信息,或者其他,

    public class TokenModelJWT
    {
        /// <summary>
        /// Id
        /// </summary>
        public long Uid { get; set; }
        /// <summary>
        /// 角色
        /// </summary>
        public string Role { get; set; }
        /// <summary>
        /// 職能
        /// </summary>
        public string Work { get; set; }

    }

 

4:將四個文件都添加好后,最后兩步

1、然后再Startup的Configure中,將TokenAuth注冊中間件

 

2、在需要加權限的頁面中,增加特性


 

這個時候,你運行項目,發現之前寫的都報錯了,


圖 7

別慌!是因為每次操作請求,都會經過TokenAuth 中的Invoke方法,方法中對Header信息進行過濾,因為現在Header中,並沒有相應的配置信息,看到這里,你就想到了,這個特別像我們常見的[HttpGet]等特性,沒錯!在.Net Core 中,到處都可以看到AOP編程,真的特別強大。

這個時候我們就用到了最開始的那個權限按鈕


,圖 8

沒錯就是這里,但是我們方法寫好了,那Token如何獲取呢,別急,我們新建一個LoginController,來模擬一次登陸操作,簡單傳遞幾個參數,將用戶角色和緩存時間傳遞,然后生成Token,並生成到緩存中,為之后做准備。

 

        /// <summary>
        /// 獲取JWT的重寫方法,推薦這種,注意在文件夾OverWrite下
        /// </summary>
        /// <param name="id">id</param>
        /// <param name="sub">角色</param>
        /// <returns></returns>
        [HttpGet]
        [Route("Token2")]
        public JsonResult GetJWTStr(long id = 1, string sub = "Admin")
        {
            //這里就是用戶登陸以后,通過數據庫去調取數據,分配權限的操作
            TokenModelJWT tokenModel = new TokenModelJWT();
            tokenModel.Uid = id;
            tokenModel.Role = sub;

            string jwtStr = JwtHelper.IssueJWT(tokenModel);
            return Json(jwtStr);
        }

 

這個時候我們就得到了我們的Token


圖 9

然后粘貼到我們的上圖權限窗口中,還記得么


圖 10

接下來,你再調用窗口,就發現都可以辣!


 

更新: 2018-10-23 

 如果這里你報錯了,可以在中間件里進行調試,看具體的原因是什么
 

 

 

 

WHAT

這一篇呢,寫的比較潦草,主要是講如何使用,具體的細節知識,還是大家摸索,還是那句話,這里只是拋磚引玉的作用喲,通過閱讀本文,你會了解到,什么是JWT,如何添加配置.net core 中間件,如何使用Token驗證,在以后的項目里你就可以在登陸的時候,調用Token,返回客戶端,然后判斷是否有相應的接口權限。

NEXT

好啦!項目准備階段就這么結束了,以后咱們就可以直接用swagger來調試了,而不是沒錯都用F5運行等,接下來我們就要正式開始搭建項目了,主要采用的是泛型倉儲模式 Repository+Service,也是一種常見的模式。

CODE

https://github.com/anjoy8/Blog.Core.git

 


注意!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。



 
粤ICP备14056181号  © 2014-2021 ITdaan.com