最近因專案需求開始接觸Web Api,在開發過程中為了要符合Restful中的「無狀態(Stateless)」,所以在資料驗證的部分就跟以往在Server Side判斷資料狀態的做法完全相反,必須把狀態儲存在Client端才符合Stateless,因此就開始在網路上看了一些資料,最後決定使用簡單又容易上手的JWT來做資料驗證。
JWT全名為JSON Web Token,是把資料加密後透過JSON的格式傳遞,總共分為3個部分header、payload和signature,並透過標點符號「.」將其串接起來,而這3個部分都會在前端透過Base64來解密,並且返回JSON格式的資料
- header包含了加密演算法及token類型這兩項數據
1 2 3 4 |
{ "alg": "HS256", "typ": "JWT" } |
- payload主要是存放資料包含JWT規範的標準數據及自定義的數據
1 2 3 4 5 6 7 |
{ iss:提供者 ==>為jwt標準數據 sub:主旨 ==> 為jwt標準數據 exp:過期時間 ==> 為jwt標準數據 iat:創建時間 ==> 為jwt標準數據 score : 80 ==> 為自定義數據 } |
- signature主要是存放對header 及 payload加密的簽章演算法的字符串(ex: HS256,HS512 …等)
最後再將組好的字串前面加上Bearer存放至Http Request的 header中
在開始時做前,可以先看一下JWT的流程圖喔~
- client 傳送帳號密碼作為驗證的資料
- server將帳號密碼透過自定義的key及演算法加密後組成JWT的格式回傳至client
- client透過base64來解密成JSON格式
- client send request時,在header 的 Authorization中加入bearer + token
- server必須使用相同的key及演算法解密驗證資料,如果沒錯即回傳client要求的資料
大致了解JWT流程之後,接下來就可以來實作囉~
step 1. 首先我們從NuGet安裝JWT的加密工具jose-jwt
step 2. 建立一個類別JwtAuthUtil.cs,負責產生token
加入此段加密產生token的程式碼
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
public class JwtAuthUtil { public string GenerateToken() { string secret = "myJwtAuthDemo";//加解密的key,如果不一樣會無法成功解密 Dictionary<string, Object> claim = new Dictionary<string, Object>();//payload 需透過token傳遞的資料 claim.Add("Account", "jim"); claim.Add("Company", "appx"); claim.Add("Department", "rd"); claim.Add("Exp", DateTime.Now.AddSeconds(Convert.ToInt32("100")).ToString());//Token 時效設定100秒 var payload = claim; var token = Jose.JWT.Encode(payload, Encoding.UTF8.GetBytes(secret), JwsAlgorithm.HS512);//產生token return token; } } |
step 3. 建立一個LoginController : ApiController 帳號密碼正確的話就呼叫這個方法後回傳token
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
public class LoginController : ApiController { public Object Post(LoginRequest loginRequest) { if( loginRequest.account == "jim" && loginRequest.password == "12345") { JwtAuthUtil jwtAuthUtil = new JwtAuthUtil(); string jwtToken = jwtAuthUtil.GenerateToken(); return new { status = true , token = jwtToken }; } else { return new { status = false, token = "Account Or Password Error" }; } } } |
step 4. 接下來我們可以先啟動專案,用PostMan來測試一下是否有成功取得token
由上圖可知我們經由帳號密碼登錄已經成功取得token了~
接下來我們每個呼叫api的動作都會使用取得的token來做身份驗證,所以我們需要加入驗證token是否正確的程式碼。 由於可能會有很多隻不同功能的api,所以我們驗證的程式把它寫在request一定會經過的ActionFilter中,就不用每隻api都需要去呼叫這段程式碼,只需把不需驗證的api在裡面做排除的動作即可。
step 5. 新增一個 JwtAuthFilter 繼承 ActionFilterAttribute,並且Override OnActionExecuting這個方法,在這個方法中我們除了要驗證Token是否正確,還會判斷token時效是否過期,並且排除Login不需驗證
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
public class JwtAuthFilter : ActionFilterAttribute { public override void OnActionExecuting(System.Web.Http.Controllers.HttpActionContext actionContext) { string secret = "myJwtAuthDemo";//加解密的key,如果不一樣會無法成功解密 var request = actionContext.Request; if (!WithoutVerifyToken(request.RequestUri.ToString())) { if (request.Headers.Authorization == null || request.Headers.Authorization.Scheme != "Bearer") { throw new System.Exception("Lost Token"); } else { //解密後會回傳Json格式的物件(即加密前的資料) var jwtObject = Jose.JWT.Decode<Dictionary<string, Object>>( request.Headers.Authorization.Parameter, Encoding.UTF8.GetBytes(secret), JwsAlgorithm.HS512); if (IsTokenExpired(jwtObject["Exp"].ToString())) { throw new System.Exception("Token Expired"); } } } base.OnActionExecuting(actionContext); } //Login不需要驗證因為還沒有token public bool WithoutVerifyToken(string requestUri) { if (requestUri.EndsWith("/Login")) return true; return false; } //驗證token時效 public bool IsTokenExpired(string dateTime) { return Convert.ToDateTime(dateTime) < DateTime.Now; } } |
接著要把這隻JwtAuthFilter註冊在Global.asax.cs的Application_Start,網站啟動時才會生效
1 |
GlobalConfiguration.Configuration.Filters.Add(new JwtAuthFilter()); |
step 6. 新增一個HomeController : ApiController來簡單的測試
1 2 3 4 5 6 7 8 9 10 |
public class HomeController : ApiController { public Object Get() { return new { status = true }; } } |
開啟PostMan,第一次我們測試不輸入Token,看是否會出現預期的錯誤
結果有出現我們自己給的Lose Token的錯誤訊息,表示Filter有成功地去驗證
接著我們輸入Token,測試是否能順利通過Filter驗證
如圖所示,輸入正確Token後,就成功通過Filter的驗證了~
針對Web Api使用JWT,以及使用PostMan來驗證Api的簡單介紹就到這裡,如有什麼問題也歡迎大家留言討論喔
以下附上程式碼連結供大家參考https://github.com/jimmingOu/WebApi.JWT