[C#] JSON Web Token(JWT)認證(authentication)授權(authorization)

Hans Jiang
Hans Revolution
Published in
9 min readJun 29, 2019

使用C#實作JSON Web Token 認證(authentication)授權(authorization)

Photo by Han Lahandoe on Unsplash

何謂 JSON Web Token

JWT(JSON Web Token)是一個開放標準(RFC 7519),定義了一種用輕量(compact)而且獨立(self-contained)的JSON物件在雙方之間安全傳輸的方式。因為透過數位簽章,資料是可被驗證及信任的。JWT可以用密鑰(secret)來簽章(透過HMAC演算法),或是用公鑰/私鑰來簽章(透過RSA或ECDSA)。

使用情境

(一) 授權(Authorization):這是最常見的JWT使用情境。當使用者登入後,每個後續請求都包含JWT,允許使用者存取該token允許的路由、服務和資源。Single Sign On是當今廣泛使用JTW的一項功能,因為它的負擔小且能輕易的跨網域使用。

(二) 資料交換(Information Exchange):JWT是一種在雙方之間安全傳輸資料的好方法。因為JWT可以簽章(例如使用公鑰/私鑰),你可以確保資料來源的正確性,此外,由於簽章是由標頭(header)跟負載(payload)計算而來,你可以驗證內容沒被篡改。

JSON Web Token結構

JWT 包含了以句點(.)分隔的三個部分:

Header(標頭)

Payload(負載)

Signature(簽章)

因此,JWT通常看起來像這樣:

JSON Web Token結構

Header(標頭):

Header通常包含兩個部分:token 的類型,即 JWT,和簽章使用的演算法,如 HMAC、SHA256 或是 RSA 等。

範例:

Header

上述 JSON 範例 以 Base64Url 編碼後即組成 JWT 的第一個部分。

(二) Payload(負載):

Payload 是token 的第二個部分,包含聲明(claims)。聲明是有關實體(通常是是使用者)和其他資料的紀載。有三種聲明:已註冊的(registered)、公開的(public)及私有的(private)

Registered claims(已註冊的聲明)是一組有用的、可交互操作的預先定義的聲明,非強制性但建議使用。部分聲明如:iss(發行者issuer)、exp(到期時間expiration time)、sub(標題subject)、aud(參與者audience)和其他。

Public claims(公開的聲明)可以由使用JWT的人隨意定義,但為了避免衝突,應該使用 JSON Web Token Registry中有定義的聲明,或是定義為包含防止衝突的命名空間的URI。

Private claims(私有的聲明)這些是在已註冊的及公開的聲明之外,雙方同意建立來分享資訊的聲明。

範例:

Payload

上述 JSON 範例以 Base64Url 編碼後即組成 JWT 的第二個部分。

注意,經過簽章的 token 雖可保護所載資訊不被篡改,但資訊內容是任何人都可以讀取的。除非有加密,否則不要在 JWT 裡面記載機密資訊。

(三) Signature(簽章): 為了要創建 Signature 部分,需取得編碼後的header、編碼後的payload、密鑰(secret)、header中指定的演算法,最後對其進行簽名。

舉例來說若使用HMAC SHA256演算法,簽名將以下面方式產生:

Signature

簽章是用來驗證訊息在傳輸過程中沒被改變,若 Token 是以私鑰進行簽章,還可以驗證 JWT 的來源如同其所宣稱。

(四) 將三個部分組合再一起:

最後產出的Token是一個由三組 Base64Url 編碼後以句點分隔的字串,可以輕易地在 HTML 及 HTTP 的環境內傳送,同時比 SAML 這類以 XML 為基底的標準更加輕量。

以下便是以前述 header payload 加上簽章後的 JWT

JWT Token Sample

若想要體驗JWT並實作這些概念,可以使用jwt.io Debugger來解碼、驗證及產生JWT

JWT運作方式

以身分驗證來說,當使用者成功登入之後,將取得JWT Token。因為Token是一個訪問令牌,必須特別注意安全問題。一般來說,不應留存所需之外的Token。當使用者要存取受保護的路由或資源時,應送出JWT,通常是在Authorization header,使用Bearer schema。Header的內容如下:

Authorization: Bearer <token>

from https://jwt.io/introduction/

1.客戶端(Client) 使用帳號密碼向授權伺服器請求授權(authorization)。

2.帳號密碼認證(authentication)成功,授權伺服器會向客戶端(Client)返回訪問令牌(Token)。

3. 客戶端(Client)使用訪問令牌(Token)來訪問受保護資源(如: API或Web資源),授權伺服器檢查訪問令牌合法後,將資源回應給客戶端(Client)。

運用範例

Client 向 Server 請求受保護資源

Client: 客戶端,可為Web browser 或 Mobile App,或其它使用者操作平台。

Server: 伺服器端,可為API 伺服器或身分驗證伺服器,或其它提供受保護層級資源的伺服器。

1.客戶端(Client)使用帳號密碼向授權伺服器請求授權(authorization)。

2.帳號密碼認證(authentication)成功,伺服器端(Server)會產生訪問令牌(Token)。

3.伺服器端(Server)回應訪問令牌(Token)給客戶端(Client)。

4.客戶端(Client)透過Authorization Header夾帶訪問令牌(Token)來請求受保護的資源。

5. 伺服器端(Server)驗證訪問令牌(Token)簽章正確

6. 伺服器端(Server)回應受保護的資源給客戶端(Client)

實作

gitHub完整程式碼如上,JWT部分主要使用 jose-jwt ,可直接由Nuget 安裝,使用上非常簡單直接參考jose-jwt gitHub上有完整說明。

Encode Jwt 參考以下程式

JwtTest/Controllers/JwtTokenController.cs

Decode Jwt 參考以下程式

JwtTest/Jwt/JwtActionFilter.cs

另外如有需自行實作JWT中,產生Token以及驗證Token,筆者也把程式放上Github了,使用的是微軟的 Microsoft.IdentityModel.Token 組件,實作上也不難。程式位置:JwtLib/MyJwt.cs

安全性議題

簽章(Signature)不可竄改性:

為實現簽章之不可竄改性,在1.2節提及之簽章密鑰(secret),為JWT驗證下最重要的一環,實務上必須以動態隨機方式產生,並妥善保存,最佳的情況下,此密鑰不為人所知。

解決方案:若以.Net實作,密鑰可透過.Net Guid.NewGuid() 產生,直接存放於Memory Cache,並啟用Cache自動回收機制,達成金鑰不可竄改性。

客戶端敏感性資料傳輸加密:

為預防中間人攻擊(Man-in-the-Middle Attack ),客戶端在傳送敏感性資訊予以伺服器端驗證時,需以動態方式加密敏感性資訊(此敏感性資料泛指客戶端為了存取伺服器端之受保護層級的資料時,所提供給伺服器作為身分驗證之唯一識別值,例如: 使用者密碼)。

解決方案:為敏感性資料設計加密機制,例如:敏感性資料 + TimeStamp + AES256 encode,若為使用者密碼或身分證ID等敏感性資料,建議採MD5或SHA等不可逆編碼機制,將密碼儲存於資料庫,如此一來客戶端傳輸時可將敏感性資料透過不可逆之演算法來傳輸,避免遭到竊取。

JWT Token 時效性:

因Token 為存取受保護資源的令牌,因考慮Token存在的時效性,且不需要的Token需即刻銷毀,以免遭竊取之後,可直接訪問受保護資源。

解決方案:建立Token回收機制

參考資料

Jwt介紹

Jwt規格

--

--

Hans Jiang
Hans Revolution

.Net C#, iOS, Android Developer; The journey to becoming a full stack Engineer.