前言
最近在看JSON Web Token(Jwt)相關(guān)的東西,但是發(fā)現(xiàn)在Nancy中直接使用Jwt的組件比較缺乏,所以就在空閑時(shí)間寫了一個(gè)。
這個(gè)組件是開源的,不過目前只支持.NET Core,后續(xù)有時(shí)間再考慮兼容,歡迎Start和提Issue。組件也已經(jīng)上傳到NuGet了,可以直接安裝使用。
項(xiàng)目地址:https://github.com/hwqdt/Nancy.Authentication.JwtBearer
NuGet地址:https://www.nuget.org/packages/Nancy.Authentication.JwtBearer/
前面也寫過在ASP.NET Core中使用的Jwt的博文,只是因?yàn)楫?dāng)時(shí)為了練習(xí)Middleware ,所以是用Middleware來處理的,實(shí)際使用是不需要那么麻煩的!
畢竟是一個(gè)Action就可以搞定的事,希望沒有誤導(dǎo)大家。
下面簡單介紹一下如何使用這個(gè)組件以及這個(gè)組件是怎么實(shí)現(xiàn)的。
簡單使用
第一步 , 用VS創(chuàng)建一個(gè)空的ASP.NET Core Web Application
第二步 , 安裝相關(guān)的NuGet包
通過命令在Package Manager Console執(zhí)行安裝下面的包,也可以用圖形界面來完成這一步操作。
Install-Package Microsoft.AspNetCore.Owin -Version 1.1.2 Install-Package Nancy -PreInstall-Package Nancy.Authentication.JwtBearer
其中,Microsoft.AspNetCore.Owin和Nancy是基礎(chǔ)包,Nancy.Authentication.JwtBearer是等下要用到的組件包。
第三步 , 修改Startup,添加對(duì)Nancy的支持。
public class Startup{ public void Configure(IApplicationBuilder app) { app.UseOwin(x=>x.UseNancy()); } }
第四步 , 添加一個(gè)Module來驗(yàn)證Nancy是否可以正常使用
public class MainModule : NancyModule{ public MainModule() { Get("/",_=> { return "test"; }); } }
正常情況下,這個(gè)時(shí)候運(yùn)行項(xiàng)目是OK的,大致效果如下:
下面一步就是添加一個(gè)Bootstrapper用于啟用JwtBearer驗(yàn)證。
public class DemoBootstrapper : DefaultNancyBootstrapper{ protected override void ApplicationStartup(TinyIoCContainer container, IPipelines pipelines) { base.ApplicationStartup(container, pipelines); var keyByteArray = Encoding.ASCII.GetBytes("Y2F0Y2hlciUyMHdvbmclMjBsb3ZlJTIwLm5ldA=="); var signingKey = new SymmetricSecurityKey(keyByteArray); var tokenValidationParameters = new TokenValidationParameters { // The signing key must match! ValidateIssuerSigningKey = true, IssuerSigningKey = signingKey, // Validate the JWT Issuer (iss) claim ValidateIssuer = true, ValidIssuer = "http://www.cnblogs.com/catcher1994", // Validate the JWT Audience (aud) claim ValidateAudience = true, ValidAudience = "Catcher Wong", // Validate the token expiry ValidateLifetime = true, ClockSkew = TimeSpan.Zero }; var configuration = new JwtBearerAuthenticationConfiguration { TokenValidationParameters = tokenValidationParameters }; //enable the JwtBearer authentication pipelines.EnableJwtBearerAuthentication(configuration); } }
如果使用過Nancy項(xiàng)目自帶的其他認(rèn)證方式(Basic,Forms和Stateless),就會(huì)發(fā)現(xiàn)下面的才是關(guān)鍵,其他的只是用于JwtBearer認(rèn)證的配置參數(shù)。
pipelines.EnableJwtBearerAuthentication(configuration);
下面簡單介紹一下配置參數(shù)。
配置參數(shù)主要有兩個(gè),一個(gè)是TokenValidationParameters , 一個(gè)是Challenge 。
其中最主要的參數(shù)TokenValidationParameters,這是用來驗(yàn)證客戶端傳過來的token是否合法的!
它位于Microsoft.IdentityModel.Tokens這個(gè)命名空間下面。
Challenge參數(shù)則是用于指定在Unauthorized時(shí)Http響應(yīng)頭中WWW-Authenticate的值。它的默認(rèn)值是Bearer
注:Challenge參數(shù)是從Microsoft.AspNetCore.Authentication.JwtBearer項(xiàng)目借鑒過來的。
到這里, 我們已經(jīng)完成了對(duì)JwtBearer認(rèn)證的配置和啟用,下面還要驗(yàn)證這個(gè)配置是否已經(jīng)生效了!
創(chuàng)建一個(gè)新的Module,并在這個(gè)Module中使用RequiresAuthentication。
public class SecurityModule : NancyModule{ public SecurityModule() : base("/demo") { //important this.RequiresAuthentication(); Get("/",_=> { return "JwtBearer authentication"; }); } }
注: 這里需要引用Nancy.Security這個(gè)命名空間
到這里,驗(yàn)證的代碼也已經(jīng)寫好了,當(dāng)我們訪問 http://yourdomain.com/demo 的時(shí)候
瀏覽器會(huì)提示我們The requested resource requires user authentication , 并且在響應(yīng)頭中我們可以看到WWW-Authenticate對(duì)應(yīng)的值是Bearer。
我們創(chuàng)建一個(gè)合法的token值,然后通過Fiddler再發(fā)起一次請(qǐng)求,看看能否正常返回我們要的結(jié)果。
下面的代碼是生成一個(gè)測試token用的,其中的JwtSecurityToken對(duì)象應(yīng)當(dāng)與前面的配置一樣,才能確保token是有效的。
private string GetJwt(){ var now = DateTime.UtcNow; var claims = new Claim[] { new Claim(JwtRegisteredClaimNames.Sub, "demo"), new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()), new Claim(JwtRegisteredClaimNames.Iat, now.ToUniversalTime().ToString(), ClaimValueTypes.Integer64) }; //must the same as your setting in your boostrapper class var symmetricKeyAsBase64 = "Y2F0Y2hlciUyMHdvbmclMjBsb3ZlJTIwLm5ldA=="; var keyByteArray = Encoding.ASCII.GetBytes(symmetricKeyAsBase64); var signingKey = new SymmetricSecurityKey(keyByteArray); var jwt = new JwtSecurityToken( issuer: "http://www.cnblogs.com/catcher1994", audience: "Catcher Wong", claims: claims, notBefore: now, expires: now.Add(TimeSpan.FromMinutes(10)), signingCredentials: new SigningCredentials(signingKey, SecurityAlgorithms.HmacSha256)); var encodedJwt = new JwtSecurityTokenHandler().WriteToken(jwt); var response = new { access_token = encodedJwt, expires_in = (int)TimeSpan.FromMinutes(10).TotalSeconds }; return JsonConvert.SerializeObject(response, new JsonSerializerSettings { Formatting = Formatting.Indented }); }
通過Fiddler執(zhí)行這個(gè)帶上了token的請(qǐng)求,大致結(jié)果如下 :
可以看到成功取到了相應(yīng)的內(nèi)容!
然后是本次測試用的token值相關(guān)的信息:
注:用Fiddler發(fā)起請(qǐng)求的時(shí)候,記得要在請(qǐng)求頭部加上Authorization,它的值是
Bearer+空格+token值
到這里,已經(jīng)展示了如何使用這個(gè)JwtBearer認(rèn)證的組件。
下面就介紹一下是怎么實(shí)現(xiàn)的這個(gè)組件!
如何實(shí)現(xiàn)
在繼續(xù)下面的內(nèi)容之前,我假設(shè)大家對(duì)Nancy的Pipelines有所了解,如果不了解的可以參考我以前的下面的鏈接
因?yàn)槠渲械腂eforePipeliine和AfterPipeline是實(shí)現(xiàn)這個(gè)認(rèn)證組件的重要切入點(diǎn)。
另外,實(shí)現(xiàn)上還用了Nancy項(xiàng)目的代碼風(fēng)格去編寫的代碼,所以你可能會(huì)發(fā)現(xiàn)與其自帶的Basic認(rèn)證等寫法差不多。
從我們上面的例子使用來說明內(nèi)部實(shí)現(xiàn)。
在上面例子的啟動(dòng)器(Bootstrapper)中,我們有一行啟用JwtBearer認(rèn)證的入口。這個(gè)入口是IPipelines的一個(gè)擴(kuò)展方法。
/// <summary>/// Module requires JwtBearer authentication/// </summary>/// <param name="pipeline">Bootstrapper to enable</param>/// <param name="configuration">JwtBearer authentication configuration</param>public static void EnableJwtBearerAuthentication(this IPipelines pipeline, JwtBearerAuthenticationConfiguration configuration){ JwtBearerAuthentication.Enable(pipeline, configuration); }
在這個(gè)擴(kuò)展方法中,調(diào)用了JwtBearerAuthentication這個(gè)靜態(tài)類的Enable方法,同時(shí)傳遞了當(dāng)前的pipeline和JwtBearer認(rèn)證的參數(shù)給這個(gè)方法。
下面是Enable方法的具體實(shí)現(xiàn)。
/// <summary>/// Enables JwtBearer authentication for the application/// </summary>/// <param name="pipelines">Pipelines to add handlers to (usually "this")</param>/// <param name="configuration">JwtBearer authentication configuration</param>public static void Enable(IPipelines pipelines, JwtBearerAuthenticationConfiguration configuration){ if (pipelines == null) { throw new ArgumentNullException("pipelines"); } if (configuration == null) { throw new ArgumentNullException("configuration"); } pipelines.BeforeRequest.AddItemToStartOfPipeline(GetLoadAuthenticationHook(configuration)); pipelines.AfterRequest.AddItemToEndOfPipeline(GetAuthenticationPromptHook(configuration)); }
以BeforeRequest為例,我們把一個(gè)委托對(duì)象加入到了請(qǐng)求之前要處理的一個(gè)集合中去。這樣在每次請(qǐng)求之前都會(huì)去處理這個(gè)委托。
所以這里有兩個(gè)部分。
請(qǐng)求處理之前的token認(rèn)證
請(qǐng)求處理之后的響應(yīng)
先來看看請(qǐng)求處理之前的token認(rèn)證如何處理
private static Func<NancyContext, Response> GetLoadAuthenticationHook(JwtBearerAuthenticationConfiguration configuration) { return context => { Validate(context,configuration); return null; }; }
這里也是一個(gè)空殼,用于返回AddItemToStartOfPipeline方法需要的委托對(duì)象。
真正處理token的還是Validate這個(gè)方法。認(rèn)證的處理還借助了System.IdentityModel.Tokens.Jwt命名空間下面的JwtSecurityTokenHandler類。
private static void Validate(NancyContext context, JwtBearerAuthenticationConfiguration configuration){ //get the token from request header var jwtToken = context.Request.Headers["Authorization"].FirstOrDefault() ?? string.Empty; //whether the token value start with Bearer if (jwtToken.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase)) { jwtToken = jwtToken.Substring("Bearer ".Length); } else { return; } //verify the token if (!string.IsNullOrWhiteSpace(jwtToken)) { try { SecurityToken validatedToken; var tokenHandler = new JwtSecurityTokenHandler(); var validatedClaims = tokenHandler.ValidateToken(jwtToken, configuration.TokenValidationParameters, out validatedToken); //var jwtSecurityToken = validatedToken as JwtSecurityToken; context.CurrentUser = validatedClaims; } catch (Exception) { } } }
要對(duì)token進(jìn)行驗(yàn)證,首先要知道token是從那里來的。常規(guī)情況下,都是將這個(gè)token放到請(qǐng)求頭的Authorization中。
所以第一步是要從請(qǐng)求頭中取出Authorization的值。這個(gè)值是必須以Bearer
開頭的一個(gè)字符串。注意是Bearer加一個(gè)空格!
而我們要驗(yàn)證的部分是去掉開頭這部分之后的內(nèi)容。只需要構(gòu)造一個(gè)JwtSecurityTokenHandler實(shí)例并調(diào)用這個(gè)實(shí)例的ValidateToken方法,并把要驗(yàn)證的token值和我們的配置傳進(jìn)去即可。
驗(yàn)證成功后,最為主要的一步是將ValidateToken方法的返回值賦給當(dāng)前Nancy上下文的CurrentUser??!
當(dāng)驗(yàn)證失敗的時(shí)候,ValidateToken方法會(huì)拋出一個(gè)異常,這里只catch了這個(gè)異常,并沒有進(jìn)行其他額外的處理。要處理無非也就是記錄日記,可以在這里trace一下,配合Diagnostics的使用。但是目前并沒有這樣做。
到這里,Before已經(jīng)OK了,現(xiàn)在要處理After了。
當(dāng)然對(duì)于After,也是只處理401(Unauthorized)的情況。主要是告訴客戶端 “當(dāng)前請(qǐng)求的資源需要用戶認(rèn)證”,并告訴客戶端當(dāng)前請(qǐng)求的資源需要那種認(rèn)證類型。
private static Action<NancyContext> GetAuthenticationPromptHook(JwtBearerAuthenticationConfiguration configuration){ return context => { if (context.Response.StatusCode == HttpStatusCode.Unauthorized) { //add a response header context.Response.WithHeader(JwtBearerDefaults.WWWAuthenticate, configuration.Challenge); } }; }
一個(gè)簡單的判斷加上響應(yīng)頭部的處理。
到這里,這個(gè)JwtBearer認(rèn)證的組件已經(jīng)ok了。
當(dāng)然這里只介紹了Pipeline的實(shí)現(xiàn),還有一個(gè)是基于NancyModule的實(shí)現(xiàn),本質(zhì)還是pipeline的處理,所以這里就不累贅了。
寫在最后
雖然簡單介紹了如何全使用和實(shí)現(xiàn)Nancy.Authentication.JwtBearer這個(gè)組件,相信大家對(duì)token(access_token)的使用是沒有太大疑問的??赡艽蠹矣幸蓡柕氖?strong style="margin: 0px; padding: 0px;">refresh_token的使用。
但是,對(duì)于refresh_token的使用,可以說因場景而異,也因人而異。只需要記住一點(diǎn)即可:refresh_token是用來換取一個(gè)新的并且可用的access_token。
本文已同步到Nancy之大雜燴
如果您認(rèn)為這篇文章還不錯(cuò)或者有所收獲,可以點(diǎn)擊右下角的【推薦】按鈕,因?yàn)槟愕闹С质俏依^續(xù)寫作,分享的最大動(dòng)力!
來源:http://catcher1994.cnblogs.com/
聲明: 本文版權(quán)歸作者和博客園共有,歡迎轉(zhuǎn)載,但未經(jīng)作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接,否則保留追究法律責(zé)任的權(quán)利。如果您發(fā)現(xiàn)博客中出現(xiàn)了錯(cuò)誤,或者有更好的建議、想法,請(qǐng)及時(shí)與我聯(lián)系?。∪绻胝椅宜较陆涣?,可以私信或者加我QQ。
http://www.cnblogs.com/catcher1994/p/7223618.htm