Json Web Token (JWT),是一個非常輕巧的規范,這個規范允許在網絡應用環境間客戶端和服務器間較安全的傳遞信息。該token被設計為緊湊且安全的,特別適用於分布式站點的單點登錄(SSO)場景。JWT一般被用來在身份提供者和服務提供者間傳遞被認證的用戶身份信息,以便於從資源服務器獲取資源。
在web應用中,我們提供的API接口,通過GET或者POST方式調用,在調用過程中,就存在着接口認證及數據的安全性問題。例如如下問題:
1、請求來自哪里,身份是否合法?
2、請求參數是否被篡改?
3、客戶端請求的唯一性,是否為重復請求攻擊(RepeatAttack)?
傳統的Session認證方式
在傳統的web應用中,服務端成功相應請求者,返回正常的response依賴於服務端通過一種存儲機制把每個用戶經過認證之后的會話信息(session)記錄服務器端,一般記錄到內存、磁盤、數據庫中,這種方式在請求量和用戶量增多的時候無疑會增大服務端的開銷;如果是記錄在內存中,那每次請求都分發登到該機器上才能授權獲取資源,那在分布式系統中就存在着問題;因為是基於Cookie的,如果Cookie被截獲,攻擊者會盜用身份信息進行發送惡意請求,也就是“跨站請求偽造”(CSRF)。
基於token的認證方式
客戶端用用戶名和密碼經過服務器認證之后,服務器會簽發一個token返回給客戶端,客戶端存儲token(一般存在請求頭里),並且在之后的請求里附帶此token,服務器每次會解簽名token,驗證通過則返回資源。另外服務端要支持CORS跨來源資源共享)策略,服務器處理完請求之后,會再返回結果中加上Access-Control-Allow-Origin。
jwt的生成
token是接口的令牌,好比去衙門辦事,“衙門口朝南開,有理無錢莫進來”。沒有令牌就別想辦事。token的驗證方法很多,也生成了很多標准,jwt就是一種基於json的RFC 7519。該標准由三部分組成:
-
header
-
payload
-
signature
header和payload經過base64編碼后用點拼接起來。signature是把header和payload編碼和拼接后經過加密算法加密,加密時還要一個密碼,這個密碼保存在服務器端。大致示意圖如下:
Header:
head由兩部分組成,一個是token類型,一個是使用的算法,如下類型為jwt,使用的算法是HS256。當然,還有HS384、HS512算法。
{
"typ": "JWT",
"alg": "HS256"
}
將以上json進行base64編碼,當然編碼前將json去格式化,如圖:
生成的編碼為:
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
用go語言實現:
package main
import (
"fmt"
"encoding/base64"
)
func main() {
head1 := `{"typ":"JWT","alg":"HS256"}`
fmt.Println(base64.StdEncoding.EncodeToString([]byte(head1)))
}
Payload:
payload 里面是 token 的具體內容,這些內容里面有一些是標准字段,我們也可以添加自定義內容。如下:
{
"iss": "smallsoup",
"iat": 1528902195,
"exp": 1528988638,
"aud": "www.smallsoup.com",
"sub": "smallsoup@qq.com",
"userId": "0418"
}
這里面的前五個字段都是由JWT的標准所定義的,在jwt標准中都可以找到。
-
iss: 該JWT的簽發者
-
sub: 該JWT所面向的用戶
-
aud: 接收該JWT的一方
-
exp(expires): 什么時候過期,這里是一個Unix時間戳
-
iat(issued at): 在什么時候簽發的
最后一個userId表示了用戶信息,為自定義字段,我們也可以定義角色等其他字段。以上的json去格式化后的base64編碼如下:
eyJpc3MiOiJzbWFsbHNvdXAiLCJpYXQiOjE1Mjg5MDIxOTUsImV4cCI6MTUyODk4ODYzOCwiYXVkIjoid3d3LnNtYWxsc291cC5jb20iLCJzdWIiOiJzbWFsbHNvdXBAcXEuY29tIiwidXNlcklkIjoiMDQxOCJ9
Signature:
JWT 的最后一部分是 Signature ,這部分內容有三個部分,先是用 Base64 編碼的 header.payload ,再用加密算法加密一下,加密的時候要放進去一個 Secret ,這個相當於是一個密碼,這個密碼秘密地存儲在服務端。
-
header
-
payload
-
secret
假設這里secret為mysecret,則用go語言實現代碼如下:
package main
import (
"fmt"
"encoding/base64"
"crypto/hmac"
"crypto/sha256"
"strings"
)
func main() {
head1 := `{"typ":"JWT","alg":"HS256"}`
head1Base64 := base64.StdEncoding.EncodeToString([]byte(head1))
payload1 := `{"iss":"smallsoup","iat":1528902195,"exp":1528988638,"aud":"www.smallsoup.com","sub":"smallsoup@qq.com","userId":"0418"}`
payload1Base64 := base64.StdEncoding.EncodeToString([]byte(payload1))
encodedstring := head1Base64 + "." + payload1Base64
hash := hmac.New(sha256.New, []byte("mysecret"))
hash.Write([]byte(encodedstring))
signature := strings.TrimRight(base64.URLEncoding.EncodeToString(hash.Sum(nil)), "=")
fmt.Printf(signature)
}
運行結果為:
fjjbA93FTcE71hz_cyIzCUFYdTdyl9hA0w7Pa0ltduc
最后這個在服務端生成並且要發送給客戶端的 Token 看起來像這樣:
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJzbWFsbHNvdXAiLCJpYXQiOjE1Mjg5MDIxOTUsImV4cCI6MTUyODk4ODYzOCwiYXVkIjoid3d3LnNtYWxsc291cC5jb20iLCJzdWIiOiJzbWFsbHNvdXBAcXEuY29tIiwidXNlcklkIjoiMDQxOCJ9.fjjbA93FTcE71hz_cyIzCUFYdTdyl9hA0w7Pa0ltduc
實際上https://jwt.io/這個網站提供了這個能力,以及各種語言的生成token和解密token的庫。
go語言生成token和解析token:
下面是go語言版的生成token和解析token的案例:
package main
import (
"github.com/dgrijalva/jwt-go"
"fmt"
)
func main() {
hmacSampleSecret := []byte("mysecret")
// Create a new token object, specifying signing method and the claims
// you would like it to contain.
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
"iss": "smallsoup",
"iat": 1528902195,
"exp": 1528988638,
"aud": "www.smallsoup.com",
"sub": "smallsoup@qq.com",
"userId": "0418",
})
// Sign and get the complete encoded token as a string using the secret
tokenString, err := token.SignedString(hmacSampleSecret)
fmt.Println(tokenString, err)
token, err = jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
// Don't forget to validate the alg is what you expect:
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"])
}
// hmacSampleSecret is a []byte containing your secret, e.g. []byte("my_secret_key")
return hmacSampleSecret, nil
})
if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid {
fmt.Println(claims)
} else {
fmt.Println(err)
}
}
具體可以了解github上以下代碼的實現。
go get github.com/dgrijalva/jwt-go
本公眾號免費提供csdn下載服務,海量IT學習資源,如果你准備入IT坑,勵志成為優秀的程序猿,那么這些資源很適合你,包括但不限於java、go、python、springcloud、elk、嵌入式 、大數據、面試資料、前端 等資源。同時我們組建了一個技術交流群,里面有很多大佬,會不定時分享技術文章,如果你想來一起學習提高,可以公眾號后台回復【2】,免費邀請加技術交流群互相學習提高,會不定期分享編程IT相關資源。
掃碼關注,精彩內容第一時間推給你