[C#]使用JWT(Json Web Token)的好處,如何實做JWT

簡單的介紹JWT相對於token和session的優點,還有如何實做

前言

因應著目前越來越多的app要和web api做溝通,還有spa的普及,還有雲端的興起,session這種古老的方式,越來越不符現代化的規劃還有安全性問題,接著就來介紹各種針對驗證常見的處理方式。

導覽

  1. Session
  2. Token
  3. Jwt(Json Web Token)
  4. 實做Jwt
  5. 結論

Session

Session已經在Web實現了很多年,但是Session會造成很多問題,第一點就是如果我們的伺服器端採用分流的方式,這時候當使用者登入到A伺服器,Session就會保存在A伺服器,但是如果因應流量而分流到B伺服器的時候,此時驗證Session就會失敗,此時就衍生出了很多種做法,比如把Session保留在Redis或Db裡面,然後所有機器驗證統一到存取的裝置做認證,這個就造成了Redis或Db的負擔,而且因為Session會回傳一個SetCookie給Client端,如下圖

之後每次跟伺服器溝通的時候,就會在request自動發送cookie去跟伺服器溝通

但是這其實就會造成資安的問題,比如CSRF的問題(http://blog.techbridge.cc/2017/02/25/csrf-introduction/),由於我們只要有setcookie了,這個cookie只要在同個domain的狀況,就都會自動發送cookie,所以顯然很容易在不自知的狀態下,就自動發送並非自己所願的request並夾帶著cookie給伺服器端。

Token

Token則是因應了現代很多裝置共同要做驗證或者spa,而產生出來的一種驗證方式,比較常見的做法就是當使用者驗證成功,產生一個guid並保存在db裡面,所以在使用者的資料表裡面就會有一個token的欄位,專門在儲存帳號和token的對應,當使用者做任何動作,就會發送一個token給伺服器,因為token可能是在寫ajax的時候,包在json裡面或放在header裡面送給伺服器,伺服器再拿這個token去確認是哪位使用者或角色的權限,但是這種驗證方式卻依然還是避免不了需要一直去跟db或redis做溝通,雖然解決了多台機器分流的問題,但是儲存方的負擔依然沒有解決。

Jwt(Json Web Token)

這個很像是Token,差異在於Jwt是一種協議,把想要保留的資訊包裝成json,然後把密錀和json透過一些演算法變成一個token,這個時候當使用者把這個token傳回伺服器端的時候,除非有人把伺服器裡面的密錀洩露出去,不然這個token是一定反解不出來的,就會造成"Invalid signature",而如果可以順利反解出來的話,我們就可以得到我們想要保留的資訊,比如使用者帳號,還有角色權限等等的,這樣子我們就不必要再去跟Db端取想要的資料和驗證。

實做Jwt

在.net使用nuget尋找會有好幾個jwt的方案,而筆者選擇的是在github上面星星數比較多的(https://github.com/jwt-dotnet/jwt),首先一樣使用nuget安裝

安裝完成之後,我們來看一下原始碼吧,為求方便我是直接在linq pad上面寫

void Main()
{
	var payload = new Dictionary<string, string> //模擬到時候伺服器想接收的資料
	{
		{ "account", "kinanson" },
		{ "role", "admin" }
	};
	IJsonSerializer serializer = new JsonNetSerializer();
	IBase64UrlEncoder urlEncoder = new JwtBase64UrlEncoder();

	string secret = "GQDstcKsx0NHjPOuXOYg5MbeJ1XT0uFiwDVvVBrk"; //自定義的密錀

	string jwt = EnCodeJwt(payload);
	jwt.Dump(); //印出json web token

	var getUserInfo = DeCodeJwt(jwt);
	getUserInfo.Dump(); //印出解開的user info

	string EnCodeJwt(Dictionary<string, string> userInfo)
	{
		IJwtAlgorithm algorithm = new HMACSHA256Algorithm();
		IJwtEncoder encoder = new JwtEncoder(algorithm, serializer, urlEncoder);
		var token = encoder.Encode(userInfo, secret);
		return token;
	}

	string DeCodeJwt(string token)
	{
		IDateTimeProvider provider = new UtcDateTimeProvider();
		IJwtValidator validator = new JwtValidator(serializer, provider);
		IJwtDecoder decoder = new JwtDecoder(serializer, validator, urlEncoder);
		var json = decoder.Decode(token, secret, verify: true);
		return json;
	}
}

結果為第一行是jwt,第二行則是我們想取得的資訊

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhY2NvdW50Ijoia2luYW5zb24iLCJyb2xlIjoiYWRtaW4ifQ.VUhl6r91KPhXM6kVgEnXtPHQLvI-BhK2rXOQ8nGTjb4
{"account":"kinanson","role":"admin"}

各位試試如果我們在DeCodeJwt裡面,把secret變成不對的密錀,就會直接拋出expection

結論

透過此篇簡單的說明和實做,希望能幫助到更多的人,如果有任何觀念錯誤的地方,再請多多指導囉。