ASP.NET Web API 2系列(四):基於JWT的token身份認證方案
阿新 • • 發佈:2020-09-23
## 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獲取。博文寫作不易希望多多支援,後續會更新更多內容,感興趣的朋友可以加關注,歡迎留言交流!也可以通過微信公眾搜尋“碼探長”,聯絡筆者!
**掃描新增下方的微信公眾號,獲取更多福利和乾貨!也可通公眾號(碼探長)聯絡探長,期待與你相遇!!!**