JWT簡介 & Golang JWT 實作

Elvin.C
6 min readMay 24, 2020

--

JWT簡介

JWT,JSON Web Token。作為現在常見的 Authorization Solution,大部分的網站會在用戶認證身份後,給予一個 JWT token 作為身份證明,之後在向網站 API 存取資源時,就可以用 JWT 進行需要認證身份的操作了。

好處

  1. 對服務提供者來說,因為驗證過的 token 內容是可以被相信的,一來可以減少對 DB 拿資料的部分負擔,二來也可以比較容易實現 Stateless。
  2. 對網路傳輸來說,相比於舊時代常用的 SAML (Security Assertion Markup Language),JWT 的內容短上很多,所以容易在網路中傳遞。
  3. 對工程師來說,JSON 很容易在各種語言中用 Map 的方式實作,所以操作起來也相對 XML 格式方便很多。

重要

JWT 的資訊不帶有保密性,只能被驗證是否被竄改而已,所以不要把機密資訊(例如密碼)放在 JWT 裡面!

JWT解析

JWT有好幾種實作方式,大部分我們現在看到的格式都是採用 JWS (JSON Web Signature) (RFC7515) 實現的,也是 JWT.io 網頁中選用的方法。JWS 格式分為 Header, Payload, Signature 三個部分:

Header

通常只會帶兩個值:alg(Algorithm) 和 typ(Type)。

  1. alg: 註明等等 Signature 部分用的 Hash 演算法,目前都用 SHA 家族的方法比較普遍。
  2. typ: 這沒有懸念,填 JWT 上去就對了。
{
"alg": "HS256",
"typ": "JWT"
}

填完之後,把這邊的資料先做 base64encoding,轉成 HeaderString 待用。

Payload

主要用來放資料的地方,標準文件裡把這邊的資訊叫做 Claims,只要用 Key-Value 的方式填上去,基本上想放什麼都可以。

比較需要注意的是,有一些當初 JWT 標準制訂時被保留的關鍵字,如果誤用了很容易造成誤會。例如 iss(Issuer), exp(Expiration Time), iat (Issued At), sub (Subject)等等,有興趣的話可以看這篇文件

{
"sub": "1234567890",
"name": "John Doe",
"iat": 1516239022
}

這邊也一樣,填完之後做 base64encoding,轉成 PayloadString 待用。

Signature

簽名部分是 JWT 的關鍵所在,也是 JWT 包含的資料可以被信任的原因。

簽名就是:

  1. 前面經過 base64encoding 準備好的 HeaderString 和 PayloadString
  2. 外加只有自己知道的 Secret
  3. 以選定的加密法(例如 SHA256)作加密

所生出來的一串無法被還原的亂碼。

這個亂碼的生成因為涉及了先前在 Header 和 Payload 填入的資料,所以如果有人偷偷改了裡面的資料,就沒辦法生成一樣的簽名,就沒辦法通過驗證。

而所謂的驗證就是,當伺服器收到 JWT 時,利用 Header 和 Payload 和自己的 Secret 重新生成一次 Signature,如果是相同的就代表驗證成功囉!

Golang JWT 實作

有了 JWT 的基礎概念後,接下來我們就用 Golang 實作看看吧。以下我們會用 github.com/dgrijalva/jwt-go 這個 library 來實作基本的 User JWT:

前置作業

如果需要多塞一些資訊的話,可以製作一個自己的 struct 來並嵌入 library 裡基本的 struct :

type DemoTokenClaims struct {
Email string
jwt.StandardClaims
}

但如果沒什麼特別需求,只需要塞 ID 之類的話,就直接用 jwt.StandardClaims 吧。

製作 JWT

如同前面介紹的, JWT 其實就是把資訊放進 JSON 裡面,再經過 base64encoding 與 Signature 組合的結果而已,所以製作 JWT 也只有以下三步驟:

  1. 填入資訊
  2. 編碼,製作token
  3. 簽名

做好之後就可以把 Token 傳回去了,請看以下範例:

func CreateDemoToken(id, email string, secret []byte) string {// 1. 填入資訊
now := time.Now()
claims := new(DemoTokenClaims)
claims.Id = id
claims.Email = email
claims.Issuer = "demoProject"
claims.IssuedAt = now.Unix()
claims.ExpiresAt = now.Add(30 * 24 * time.Hour).Unix()
// 2. 編碼,製作token
demoToken := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
// 3. 簽名
tokenString, _ := demoToken.SignedString(secret)
return tokenString
}

驗證 Token

當我們收到 JWT 時,需要先確認資訊有沒有被竄改,同樣地,利用這包 jwt library 可以很輕鬆的完成:

func ValidDemoToken(demo string, secret []byte) (*DemoTokenClaims, error) {// base64decoding + 驗證 + 回傳 raw token
token, err := jwt.ParseWithClaims(demo, &DemoTokenClaims{}, func(token *jwt.Token) (interface{}, error) {
return secret, nil
})
if err != nil {
return nil, errors.New("invalid")
}
// 從 raw token 中取回資訊
if claims, ok := token.Claims.(*DemoTokenClaims); ok && token.Valid {
return claims, nil
}
return nil, errors.New("invalid")
}

完成驗證並取回資訊後,就可以繼續後續的動作囉。

參考資料

RFC7519
JSON Web Token (JWT) draft-ietf-oauth-json-web-token-32
Introduction to JSON Web Tokens
Go dev — package jwt
不要用JWT替代session管理(上):全面了解Token,JWT,OAuth,SAML,SSO

--

--

Elvin.C

後端工程師/藥師,主要語言是 Golang 和 Node.js,喜歡打拳和看電影,天生勞碌命體質,最大的願望是能做出新一代的醫學應用軟體。