本篇將在webapi項目中加入身份認證功能,僅對授權的用戶提供服務,未授權的訪問給出提示信息。
1. 在傳統的web身份認證中,典型的認證過程是這樣的:用戶通過瀏覽器打開登錄頁,輸入帳號/密碼后提交,
Web服務器判斷帳號/密碼是否正確,如果正確會在服務器中生成一個Session對象標識用戶身份,
同時在響應Header中設置對應的Cookie字符串傳遞回瀏覽器 ;
當用戶再次訪問頁面的時候,瀏覽器發送的請求頭會帶上此Cookie字符串,Web服務器獲取請求頭中的Cookie字符串並解析,
然后與服務器中的Session對象比較,找出對應的Session,后續代碼中就可以使用代表該用戶身份的session信息了,如下圖:
2. .net core webapi是以微服務的方式來提供數據和計算服務的,自然需要使用更加合適的身份驗證模式,
為此微軟為我們提供了JWT(JSON Web Token)的認證授權實現,這是一種基於token的鑒權機制,
它不需要在服務端用Session去保存用戶的認證或會話信息,
只需要在請求/響應Header中加入簽名后的字符串(稱之為Token)就可以了,如下圖:
可以看到已經不需要在Web服務器上保存Session並維護Session Map信息了。
3. JWT認證生成的Token字符串格式如下 :
jouf980uojfosadjdfhaksd.sf23er4wehyuty3fasdf2sdzxz7xcvghret2kko9werdfartyy.cfg54dskj7nyuhj89sdfghj
它由三部分組成,分別是頭信息、有效載荷、簽名,中間以(.)分隔,每段的作用如下:
第一部分:header(頭信息)
編碼前的 header 信息包含算法(默認是 HMAC SHA256)和token類型定義(采用json格式,形式如下),
1 { 2 "alg": "HS256", 3 "typ": "JWT" 4 }
對此json對象進行Base64URL加密生成一個字符串,形如 "jouf980uojfosadjdfhaksd" 。
第二部分:Payload(有效載荷)
包含Claims(聲明),用來存放實際需要傳遞的數據,也是一個 JSON 對象,如下:
1 { 2 "sub": "testabcde", 3 "exp":"2020/01/02 09:20:39", 4 "jti":1034758934 5 "userid":9527 6 "username": "John Doe", 7 }
對此json對象進行Base64URL加密生成一個字符串,形如 "sf23er4wehyuty3fasdf2sdzxz7xcvghret2kko9werdfartyy" 。
Claims 中的字段名稱可以使用官方推薦的名稱也可以自己定義,如下表所示:
序號 | 名稱 | 描述 | 類別 |
1 | iss (issuer) | 簽發人 | 官方字段 |
2 | exp (expiration time) | 過期時間 | 官方字段 |
3 | sub (subject) | 主題 | 官方字段 |
4 | aud (audience) | 受眾 | 官方字段 |
5 | nbf (Not Before) | 生效時間 | 官方字段 |
6 | iat (Issued At) | 簽發時間 | 官方字段 |
7 | jti (JWT ID) | 編號 | 官方字段 |
8 | xxxx | 用戶自定義 | 私有字段 |
... | xxxx | 用戶自定義 | 私有字段 |
n | xxxx | 用戶自定義 | 私有字段 |
第三部分:Signature(簽名)
簽名的作用就是驗證前面兩部分的內容是否有被篡改,生成方式如下:
先將 "編碼過的header字符串"和"編碼過的payload字符串" 用 "." 拼接起來,
然后使用 Header 里面指定的簽名算法(默認是 HMAC SHA256)配合指定的密鑰(secret)對它們簽名,
最后將簽名的結果用Base64URL加密生成一個字符串,形如 "cfg54dskj7nyuhj89sdfghj" 。
注:因為Base64URL實際是對字符串做了一下轉換,大部分語言都是可以對其進行解密的,
所以對於敏感數據的傳遞需要先加密,加密方式可以參考本系列第十二篇。
4. .net5 core webapi中的編碼實現。
有了前面對原理的了解再編碼實現就比較簡單了,步驟如下:
4.1 先安裝 System.IdentityModel.Tokens.Jwt 和 Microsoft.AspNetCore.Authentication.JwtBearer 這兩個包。
安裝后在解決方案資源管理器中就可以看到這2個包了,如下:
4.2 在Startup類的 ConfigureServices( ) 方法中配置JWT,增加如下代碼(紅色標注):
1 public void ConfigureServices(IServiceCollection services) 2 { 3 services.AddControllers(); 4 5 services.AddScoped<IUserDao, MySqlUserDao>(); 6 7 services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(); 8 9 }
4.3 在 Startup 類的 Configure( ) 方法中啟用JWT,代碼如下(紅色標注):
1 public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerFactory loggerFactory) 2 { 3 if (env.IsDevelopment()) 4 { 5 app.UseDeveloperExceptionPage(); 6 7 } 8 9 loggerFactory.AddFile("Logs/log{Date}.txt"); 10 11 app.UseRouting(); 12 13 app.UseAuthentication(); 14 15 app.UseAuthorization(); 16 17 app.UseEndpoints(endpoints => 18 { 19 endpoints.MapControllers(); 20 }); 21 }
4.4 在UsersController.cs中使用Jwt認證服務。
在終結點 ManageUsers( ) 上使用Jwt認證服務之前,我們先訪問一下API,看Jwt認證生效前是什么效果。
打開POSTMAN,訪問http://localhost:52384/api/users,結果如下:
可以看到在終結點 ManageUsers( ) 沒有使用Jwt認證時,可以正常訪問。
在UsersController.cs中添加引用 using Microsoft.AspNetCore.Authorization;
然后在終結點ManageUsers( )上加[Authorize]屬性啟用
Jwt認證服務,代碼如下(紅色標注):
1 [HttpGet] 2 [Authorize] 3 public ContentResult ManageUsers() 4 { 5 //... 6 }
重新編譯項目,然后在POSTMAN中訪問 http://localhost:52384/api/users,結果如下:
此時響應的是"401 Unauthorized" 沒有授權,並且在響應Header中多了一個"WWW-Authenticate"的KEY,Value="Bearer",Jwt身份認證已經生效了。
另:如果要對UsersController中所有終結點都啟用Jwt身份認證,只需要在UsersController這個類名上加[Authorize]屬性
就可以了。