1. 程式人生 > >ASP.NET Web API 2系列(四):基於JWT的token身份認證方案

ASP.NET Web API 2系列(四):基於JWT的token身份認證方案

## 1.引言 通過前邊的系列教程,我們可以掌握WebAPI的初步運用,但是此時的API介面任何人都可以訪問,這顯然不是我們想要的,這時就需要控制對它的訪問,也就是WebAPI的許可權驗證。驗證方式非常多,本文就重點介紹一種常用的驗證方式:基於JWT的token身份認證方案。 ## 2.前期回顧 [Web API系列(一):初識API及手動搭建基本框架](https://mp.weixin.qq.com/s?__biz=MjM5NTgxODAzNw==&mid=2247483718&idx=1&sn=37f2915d168c9f8b6822907f2dd66a16&chksm=a6f3fe1c9184770a68e3e11049af9bb0314c5e2a96401db72fbd42b3f51555f1f567ba5c5321&sessionid=1598198567&scene=126&subscene=0&clicktime=1598198645&enterid=1598198645&ascene=3&devicetype=android-25&version=2700113f&nettype=cmnet&abtest_cookie=AAACAA%3D%3D&lang=zh_CN&exportkey=AZRAoZV7yfmDruaqJiP%2B2qQ%3D&pass_ticket=gTqwDqmQJMoL5qt7%2FaYDLOjLJq9l%2Bi7hSHIeLQ4EZkeFMmkMXkUpFSMt0MMUczVC&wx_header=1) [Web API系列(二):靈活多樣的路由配置](https://mp.weixin.qq.com/s?__biz=MjM5NTgxODAzNw==&mid=2247483776&idx=1&sn=b5c32e4d07e12c82f207793cceef534c&chksm=a6f3feda918477cc436eadea8318166becd7d5fed63f4b8166f9e5b7f19d26c49bc86c16db98&sessionid=1598199049&scene=126&subscene=0&clicktime=1598199060&enterid=1598199060&ascene=3&devicetype=android-25&version=2700113f&nettype=cmnet&abtest_cookie=AAACAA%3D%3D&lang=zh_CN&exportkey=AVamcd22VHq%2BxuIKPJjuhbg%3D&pass_ticket=gTqwDqmQJMoL5qt7%2FaYDLOjLJq9l%2Bi7hSHIeLQ4EZkeFMmkMXkUpFSMt0MMUczVC&wx_header=1) [Web API系列(三):新增介面詳細說明及測試](https://mp.weixin.qq.com/s?__biz=MjM5NTgxODAzNw==&mid=2247483809&idx=1&sn=22e829777bfd9c9f4b8c871b30bc5b0e&chksm=a6f3fefb918477edef82baa41e3c2901b588465cfdbbd8802ca5b4fd3ddbd49d9dfa2f5b5e0c&scene=0&xtrack=1&key=53e823c17d388498a097f7003d25f77738ad16ba4da34419ddb5d2a2ce6294f6bb7312c71de6ce7f2bb67584a1d055accbadc02a108fe98421af9aef6084cab39e7037338bd99c24e947ae4353ba084422dec28a4928bf2e19c14221a224fbe4b14778a124043ca512b3f48afa28590e416e457d9fd487e80186dcccdf229f47&ascene=1&uin=NzQyNDQxODYx&devicetype=Windows+10+x64&version=62090529&lang=zh_CN&exportkey=AWPey2atIs4ilCccJ5%2BEra0%3D&pass_ticket=iXSPlmN8KuxXXMctGUtuGKeygGgLqDi8YPQ%2BKxyeS1q44eM%2Fok%2BnBto9fchz51z1&wx_header=0) ## 3.認識JWT JWT是 JSON Web Token 的縮寫,是一個開放標準(RFC 7519),它定義了一種緊湊的、自包含的方式,用於作為JSON物件在各方之間安全地傳輸資訊。該資訊可以被驗證和信任,因為它是數字簽名的。 ### 3.1 JWT工作流程 這裡我們通過一張圖瞭解它的工作流程。 ![](https://img2020.cnblogs.com/blog/1033899/202009/1033899-20200923003101899-258548922.png) 從上圖中我們可以看出它是**基於Token的身份認證**,具體流程:客戶端攜帶使用者名稱和密碼請求訪問 - 伺服器校驗使用者憑據 - 應用提供一個token給客戶端 - 客戶端儲存token,並且在隨後的每一次請求中都帶著它 -伺服器校驗token並返回資料。 ### 3.2JWT結構 JSON Web Token由三部分組成,它們之間用圓點(.)連線。這三部分分別是: + Header:頭部,它有token的型別(“JWT”)和演算法名稱(比如:HMAC SHA256或者RSA等等)兩部分組成; + **Payload**:荷載,它包含宣告(要求)。宣告是關於實體(通常是使用者)和其他資料的宣告; + Signature:簽名,目的是用來驗證頭部和載荷是否被非法篡改。 通過下圖,我們可以直觀的看到JWT的組成。 ![](https://img2020.cnblogs.com/blog/1033899/202009/1033899-20200923003124512-377030663.png) 它本質上是一個獨立的身份驗證令牌,可以包含使用者標識、使用者角色和許可權等資訊,以及您可以儲存任何其他資訊(自包含)。任何人都可以輕鬆讀取和解析,並使用金鑰來驗證真實性。 ## 4.具體實現 上文介紹了JWT的原理,讀者簡單瞭解即可,這裡我們通過具體程式碼來實現。 ### 4.1安裝JWT包 通過NuGget管理工具安裝JWT包,如下圖 ![](https://img2020.cnblogs.com/blog/1033899/202009/1033899-20200923003143702-1375437547.png) ### 4.2新增LoginRequest、AuthInfo和HttpResult三個實體類 在MyWebAPI.Entities中新增相應類 LoginRequest實體 public class LoginRequest { public string UserId { get; set; } public string Password { get; set; } } AuthInfo實體類 public class AuthInfo { public string UserId { get; set; } public DateTime Expires { get; set; } } HttpResul實體類 public class HttpResult { public bool Success { get; set; } public dynamic Data { get; set; } public string Message { get; set; } } ### 4.3新增SystemController,並新增Login登入方法 具體程式碼如下: [RoutePrefix("api/System")] public class SystemController : ApiController { [HttpPost, Route("Login")] public HttpResult Login([FromBody] LoginRequest loginRequest) { if (loginRequest == null) return new HttpResult() { Success = false, Message = "登入資訊為空!" }; #region 通過資料庫判斷登入資訊是否正確(這裡簡化判斷) if (loginRequest.UserId != "admin" || loginRequest.Password != "admin") { return new HttpResult() { Success = false, Message = "使用者名稱和密碼不正確!" }; } #endregion AuthInfo authInfo = new AuthInfo() { UserId = loginRequest.UserId, Expires = DateTime.Now.AddDays(1) }; const string secretKey = "matanzhang";//口令加密祕鑰(應該寫到配置檔案中) byte[] key = Encoding.UTF8.GetBytes(secretKey); IJwtAlgorithm algorithm = new HMACSHA256Algorithm();//加密方式 IJsonSerializer serializer = new JsonNetSerializer();//序列化Json IBase64UrlEncoder urlEncoder = new JwtBase64UrlEncoder();//base64加解密 IJwtEncoder encoder = new JwtEncoder(algorithm, serializer, urlEncoder);//JWT編碼 var token = encoder.Encode(authInfo, key);//生成令牌 return new HttpResult() { Success = true, Data = token,Message = "登入成功!"}; } } ### 4.4新增API過濾器ApiAuthorizeAttribute 具體程式碼如下: public class ApiAuthorizeAttribute: AuthorizeAttribute { protected override bool IsAuthorized(HttpActionContext actionContext) { try { var authHeader = from t in actionContext.Request.Headers where t.Key == "auth" select t.Value.FirstOrDefault(); var enumerable = authHeader as string[] ?? authHeader.ToArray(); string token = enumerable.FirstOrDefault(); if (string.IsNullOrEmpty(enumerable.FirstOrDefault())) return false; const string secretKey = "matanzhang";//口令加密祕鑰(應該寫到配置檔案中) byte[] key = Encoding.UTF8.GetBytes(secretKey); IJsonSerializer serializer = new JsonNetSerializer(); IDateTimeProvider provider = new UtcDateTimeProvider(); IJwtValidator validator = new JwtValidator(serializer, provider); IBase64UrlEncoder urlEncoder = new JwtBase64UrlEncoder(); IJwtAlgorithm algorithm = new HMACSHA256Algorithm(); IJwtDecoder decoder = new JwtDecoder(serializer, validator, urlEncoder, algorithm); //解密 var authInfo = decoder.DecodeToObject(token, key, verify: true); if (authInfo != null) { //判斷口令過期時間 if (authInfo.Expires < DateTime.Now) { return false; } actionContext.RequestContext.RouteData.Values.Add("auth", authInfo); return true; } } catch (Exception e) { } return false; } /// /// 處理授權失敗的請求 /// /// protected override void HandleUnauthorizedRequest(HttpActionContext actionContext) { var erModel = new HttpResult() { Success = false, Message = "身份認證不正確!" }; actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.OK, erModel, "application/json"); } } ### 4.5在StudentController中新增過濾屬性ApiAuthorize 具體如下: [RoutePrefix("api/Student"),ApiAuthorize] public class StudentController : ApiController { private static readonly List StudentList = new List() { new Student() {Id = "001", Name = "張三", Sex = "男", Age = 19, Dept = "軟體學院"}, new Student() {Id = "002", Name = "李麗", Sex = "女", Age = 19, Dept = "資環學院"} }; [HttpGet] public IEnumerable Get() { return StudentList; } [HttpGet, Route("GetByDept/{dept}")] public IEnumerable GetByDept(string dept) { List tempList = StudentList.Where(p => p.Dept == dept).ToList(); return tempList; } [HttpGet] public Student Get(string id) { List tempList = StudentList.Where(p => p.Id == id).ToList(); return tempList.Count == 1 ? tempList.First() : null; } [HttpPost] public bool Post([FromBody] Student student) { if (student == null) return false; if (StudentList.Where(p => p.Id == student.Id).ToList().Count > 0) return false; StudentList.Add(student); return true; } [HttpPut] public bool Put(string id, [FromBody] Student student) { if (student == null) return false; List tempList = StudentList.Where(p => p.Id == id).ToList(); if (tempList.Count == 0) return false; Student originStudent = tempList[0]; originStudent.Name = student.Name; originStudent.Sex = student.Sex; originStudent.Age = student.Age; originStudent.Dept = student.Dept; return true; } [HttpDelete] public bool Delete(string id) { List tempList = StudentList.Where(p => p.Id == id).ToList(); if (tempList.Count == 0) return false; StudentList.Remove(tempList[0]); return true; } } 依照步驟新增相關程式碼,此時就完成了JWT驗證的新增。 ## 5.通過PostMan測試程式 執行VS,檢視相關API介面,如下圖所示。 ![](https://img2020.cnblogs.com/blog/1033899/202009/1033899-20200923003226982-1356016100.png) 登入前,測試Get:`http://localhost:44321/api/Student`介面,返回結果如下圖所示。 ![](https://img2020.cnblogs.com/blog/1033899/202009/1033899-20200923003246372-649669167.png) 登入,測試Post:`http://localhost:44321/api/System/Login`介面,返回結果如下圖所示。 ![](https://img2020.cnblogs.com/blog/1033899/202009/1033899-20200923003305947-840833833.png) 登入後,測試Get:`http://localhost:44321/api/Student`介面,返回結果如下圖所示。 ![](https://img2020.cnblogs.com/blog/1033899/202009/1033899-20200923003327315-310357381.png) 在APIController上新增許可權驗證後,訪問相應介面時,需要在header裡面新增auth屬性(token),這樣就完成了身份認證。 ## 6.總結 本文介紹了JWT的原理,然後通過程式碼完成了相應例項教程,博文中的原始碼可以通過筆者GitHUb獲取。博文寫作不易希望多多支援,後續會更新更多內容,感興趣的朋友可以加關注,歡迎留言交流!也可以通過微信公眾搜尋“碼探長”,聯絡筆者! **掃描新增下方的微信公眾號,獲取更多福利和乾貨!也可通公眾號(碼探長)聯絡探長,期待與你相遇!!!**