ASP.NET Core的JWT的實現(中介軟體)
既然選擇了遠方,便只顧風雨兼程 __ HANS許
-
-
-
-
- ASP.NET Core 的Middleware實現
-
-
-
引言:挺久沒更新了,之前做了Vue的系列,後面想做做服務端的系列,上下銜接,我們就講講WebApi(網路應用程式介面),介面免不了使用者認證,所以接下來我們的主題系列文章便是“ 基於ASP.NET Core的使用者認證 ”,分為市面上流行的JWT(JSON WebToken)與OAuth2(開放授權)
JWT(JSON Web Token)
-
什麼叫JWT
JSON Web Token(JWT)是目前最流行的跨域身份驗證解決方案。
一般來說,網際網路使用者認證是這樣子的。
1、使用者向伺服器傳送使用者名稱和密碼。
2、伺服器驗證通過後,在當前對話(session)裡面儲存相關資料,比如使用者角色、登入時間等等。
3、伺服器向用戶返回一個 session_id,寫入使用者的 Cookie。
4、使用者隨後的每一次請求,都會通過 Cookie,將 session_id 傳回伺服器。
5、伺服器收到 session_id,找到前期儲存的資料,由此得知使用者的身份。
伺服器需要儲存session,做持久化,這種模式沒有分散式架構,無法支援橫向擴充套件 ,如果真的要的話就必須採用分散式快取來進行管理Seesion。那JWT相反,它儲存的是在客戶端,每次請求都將JWT代入伺服器,進行簽名,許可權驗證。JWT由客戶端請求,服務端生成,客戶端儲存,服務端驗證。
-
JWT的原理與格式
-
原理
在上面,我們也講過了,簡單的來說,我們將伺服器需要驗證我們的條件(賬戶,密碼等等),發給伺服器,伺服器認證通過,生成一個JSON物件返回給我們,例如下面。當然,為了防止被篡改,所以我們會將物件加密,再次請求伺服器,需要將jwt放在請求頭部,傳遞給伺服器,來判斷許可權等等。
- {
- "姓名" :"張三" ,
- "角色" :"管理員" ,
- "到期時間" :"2018年7月1日0點0分"
- }
-
格式
- 實際格式
- eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
- eyJBIjoiQUFBQSIsIkIiOiJCQkJCIiwiQyI6IkNDQ0MiLCJ1ZXIiOiJ4dWh1YWxlIiwib3BlbmlkIjoiNTE1NjEzMTM1MTYzMjEiLCJmZiI6ImRmc2RzZGZzZGZzZHMiLCJuYmYiOjE1NTIyMTE4NjAsImV4cCI6MTU1MjIxMzY2MH0.
- 16m57YnnIcgIth25dwphQKPYuIq42BVmZV6LIBO7KDg
它是一個很長的字串,中間用點(.)分隔成三個部分。注意,JWT內部是沒有換行的,這裡只是為了便於展示,將它寫成了幾行。JWT 的三個部分依次如下。
- Header(頭部)
- Payload(負載)
- Signature(簽名)
簡單講下,
Header
描述加密演算法與token型別,Payload
描述的是實際需要傳遞的資料,如失效時間,簽發人等等,Signature
描述的是一段對於前面兩部部分的簽名,當然祕鑰只有伺服器才知道。
-
簡單的介紹下JWT,更多的話,可以這邊文章 看看。我們著重講下實現。
ASP.NET Core 的Middleware實現
-
建立JWT
首先我們要先建立token,畢竟這個是最重要的。Core自帶JWT幫助類,所以我們按照幫助類的意思來寫個方法建立token。
- public string CreateJsonWebToken ( Dictionary<string ,string > payLoad )
- {
- if (string .IsNullOrWhiteSpace(setting.SecurityKey))
- {
- throw new ArgumentNullException("JsonWebTokenSetting.securityKey" ,
- "securityKey為NULL或空字串。請在\"appsettings.json\"配置\"JsonWebToken\"節點及其子節點\"securityKey\"" );
- }
- var now = DateTime.UtcNow;
- // You can add other claims here, if you want:
- var claims =new List<Claim>();
- foreach (var keyin payLoad.Keys)
- {
- var tempClaim =new Claim(key, payLoad[key]?.ToString());
- claims.Add(tempClaim);
- }
- // Create the JWT and write it to a string
- var jwt =new JwtSecurityToken(
- issuer:null ,
- audience:null ,
- claims: claims,
- notBefore: now,
- expires: now.Add(TimeSpan.FromMinutes(setting.ExpiresMinute)),
- signingCredentials:new SigningCredentials(new SymmetricSecurityKey(Encoding.ASCII.GetBytes(setting.SecurityKey)), SecurityAlgorithms.HmacSha256));
- var encodedJwt =new JwtSecurityTokenHandler().WriteToken(jwt);
- return encodedJwt;
- }
從方法我們看到,我們傳入的是負載這個片段,而失效時間與祕鑰我們是放在了
appsettings.json
來進行配置的。使用DI來獲取配置檔案中的節點值。 -
編寫中介軟體
我們都知道,中介軟體是Core的管道模型組成部分,所以我們在中介軟體做驗證,來判斷每次請求使用者是有有許可權是有該WebApi或者其他API。
-
中介軟體
- public JwtCustomerAuthorizeMiddleware ( RequestDelegate next, IOptions<JsonWebTokenSetting> options, IJsonWebTokenValidate jsonWebTokenValidate, Func<Dictionary<string ,string >, JsonWebTokenSetting,bool > validatePayLoad, List<string > anonymousPathList )
- {
- this ._next = next;
- this ._setting = options.Value;
- this ._jsonWebTokenValidate = jsonWebTokenValidate;
- this ._validatePayLoad = validatePayLoad;
- this ._anonymousPathList = anonymousPathList;
- }
- public async TaskInvoke (HttpContext context )
- {
- //JsonWebTokenValidate
- //若是路徑可以匿名訪問,直接跳過
- if (_anonymousPathList.Contains(context.Request.Path.Value))
- {
- //還未驗證
- await _next(context);
- return ;
- }
- var result = context.Request.Headers.TryGetValue("Authorization" ,out StringValues authStr);
- if (!result ||string .IsNullOrEmpty(authStr.ToString()))
- {
- throw new UnauthorizedAccessException("未授權,請傳遞Header頭的Authorization引數。" );
- }
- //進行驗證與自定義驗證
- result = _jsonWebTokenValidate.Validate(authStr.ToString().Substring("Bearer " .Length).Trim()
- , _setting, _validatePayLoad);
- if (!result)
- {
- throw new UnauthorizedAccessException("驗證失敗,請檢視傳遞的引數是否正確或是否有許可權訪問該地址。" );
- }
- await _next(context);
- }
從程式碼來看,
anonymousPathList
是URL路徑,若是在這個List
內的URL,便可直接跳過驗證,接著將
authStr
token代入驗證函式,validatePayLoad
卻是我們自代入的委託函式,用於伺服器自定義驗證。-
驗證
驗證方法,我只是做了簽名驗證與時間驗證。並沒有定得死死的,讓使用者自由度的去進行驗證。
- public bool Validate ( string encodeJwt, JsonWebTokenSetting setting, Func<Dictionary<string ,string >, JsonWebTokenSetting,bool > validatePayLoad )
- {
- if (string .IsNullOrWhiteSpace(setting.SecurityKey))
- {
- throw new ArgumentNullException("JsonWebTokenSetting.securityKey" ,
- "securityKey為NULL或空字串。請在\"appsettings.json\"配置\"JsonWebToken\"節點及其子節點\"securityKey\"" );
- }
- var success =true ;
- var jwtArr = encodeJwt.Split('.' );
- var header = JsonConvert.DeserializeObject<Dictionary<string ,string >>(Base64UrlEncoder.Decode(jwtArr[0 ]));
- var payLoad = JsonConvert.DeserializeObject<Dictionary<string ,string >>(Base64UrlEncoder.Decode(jwtArr[1 ]));
- var hs256 =new HMACSHA256(Encoding.ASCII.GetBytes(setting.SecurityKey));
- //首先驗證簽名是否正確(必須的)
- success = success &&string .Equals(jwtArr[2 ], Base64UrlEncoder.Encode(hs256.ComputeHash(Encoding.UTF8.GetBytes(string .Concat(jwtArr[0 ],"." , jwtArr[1 ])))));
- if (!success)
- {
- return success;//簽名不正確直接返回
- }
- //其次驗證是否在有效期內(也應該必須)
- var now = ToUnixEpochDate(DateTime.UtcNow);
- success = success && (now >=long .Parse(payLoad["nbf" ].ToString()) && now <long .Parse(payLoad["exp" ].ToString()));
- //再其次 進行自定義的驗證
- success = success && validatePayLoad(payLoad, setting);
- return success;
- }
-
中介軟體
-
載入中介軟體
- 使用擴充套件方法,來封裝中介軟體
- public static IApplicationBuilderUseJwtCustomerAuthorize ( this IApplicationBuilder app, Action<IJwtCustomerAuthorezeOption> action )
- {
- var _JwtCustomerAuthorezeOption = app.ApplicationServices.GetService<IJwtCustomerAuthorezeOption>()as JwtCustomerAuthorezeOption;//new JwtCustomerAuthorezeOption();
- action(_JwtCustomerAuthorezeOption);
- return app.UseMiddleware<JwtCustomerAuthorizeMiddleware>(_JwtCustomerAuthorezeOption.validatePayLoad, _JwtCustomerAuthorezeOption.anonymousPath);
- }
-
在
Startup.cs
使用
-
註冊服務
- public void ConfigureServices (IServiceCollection services ) {
- services.AddJwt(Configuration);}
-
使用中介軟體
- public void Configure (IApplicationBuilder app, IHostingEnvironment env )
- {
- app.UseJwtCustomerAuthorize(option =>
- {
- //設定不會被驗證的url,可以使用鏈式呼叫一直新增
- option.SetAnonymousPaths(new System.Collections.Generic.List<string >()
- {
- //"/",
- "/Home/Privacy" ,
- "/Home/CreateJsonToken"
- });
- option.SetValidateFunc((playLoad, sertting) =>
- {
- return true ;
- });
- });
- }
總結下,通過上面,就完成了JWT在ASP.NET Core使用中介軟體的方式的實現。簡單來說就是用自帶方法建立token,驗證則使用中介軟體的形式,每次請求都需要進行驗證當然你可以設定特殊URL。在下篇文章我們來講講使用策略模式的JWT實現。