從壹開始前後端分離【 .NET Core2.0 +Vue2.0 】框架之五 || Swagger的使用 3.3 JWT許可權驗證【修改】
重大更新:
兩個是上下銜接的,主要是解決本文中,過期時間無效的問題。
群友反饋:
群裡有小夥伴反饋,在Swagger使用的時候報錯,無法看到列表,這裡我說下如何除錯和主要問題:
1、如果遇到問題,這樣的:
請在瀏覽器 =》 F12 ==》 console 控制檯 ==》點選錯誤資訊地址
或者直接連結http://localhost:xxxxx/swagger/v1/swagger.json,就能看到錯誤了
更新:
1、目前已經實現多使用者對介面的訪問,也可以通過將使用者的資訊自定義存入到 Token 中,然後反序列化後,將該使用者的多個角色一起新增到 clain 中,實現一個使用者的多角色訪問。
下文中包含以下字樣的地方:注意這個可以新增多個角色宣告,請注意這是一個 list
2、網友@ Jwt token時效不起作用,有知道的小夥伴,可以回答下。
提要
如何給介面實現許可權驗證?
其實關於這一塊,我思考了下,因為畢竟我的專案中是使用的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" }); #endregionView 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 = "[email protected]", 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可以進一步判斷相應的許可權等。
接下來,就是在專案中新增五個檔案,如下圖
,圖 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,也是一種常見的模式。