jwt-go筆記
這個(gè)庫(kù)已經(jīng)沒(méi)人維護(hù)了,他們現(xiàn)在新搞了一個(gè)询一,具體可以去github上看
jwt介紹
jwt(json web token)是一種用于前后端身份認(rèn)證的方法,一個(gè)jwt由header隐孽,payload,和signature組成缓醋。
- header:包含了token類型和算法類型
- payload:包含了一些用戶自定義或jwt預(yù)定義的一些數(shù)據(jù),每一個(gè)數(shù)據(jù)叫一個(gè)claim,注意不要將敏感信息放入
- signature:將header和payload經(jīng)過(guò)base64編碼,加上一個(gè)secret密鑰绊诲,整體經(jīng)過(guò)header中的算法加密后生成
jwt-go重要的幾個(gè)結(jié)構(gòu)
1.Claims
type Claims interface {
Valid() error
}
claims是一個(gè)實(shí)現(xiàn)了Valid方法的interface,Valid方法用于判斷該claim是否合法
2.Keyfunc
type Keyfunc func(*Token) (interface{}, error)
Keyfunc在使用時(shí)一般都是返回secret密鑰掂之,可以根據(jù)Token的種類不同返回不同的密鑰.
官方文檔:This allows you to use properties in the Header of the token (such as 'kid') to identify which key to use.
3.Mapclaims
type MapClaims map[string]interface{}
一個(gè)用于放decode出來(lái)的claim的map,有Vaild和一系列VerifyXXX的方法
4.Parser
type Parser struct {
ValidMethods []string // 有效的加密方法列表抗俄,如果不為空,則Parse.Method.Alg()必需是VaildMethods的一種动雹,否則報(bào)錯(cuò)
UseJSONNumber bool // Use JSON Number format in JSON decoder
SkipClaimsValidation bool // 在解析token時(shí)跳過(guò)claims的驗(yàn)證
}
用來(lái)將tokenstr轉(zhuǎn)換成token
5.SigningMethod
type SigningMethod interface {
Verify(signingString, signature string, key interface{}) error // Returns nil if signature is valid
Sign(signingString string, key interface{}) (string, error) // Returns encoded signature or error
Alg() string // returns the alg identifier for this method (example: 'HS256')
}
簽名方法的接口,可以通過(guò)實(shí)現(xiàn)這個(gè)接口自定義簽名方法跟压,jwt-go內(nèi)置一些實(shí)現(xiàn)了SigningMethod的結(jié)構(gòu)體
6.StandardClaims
type StandardClaims struct {
Audience string `json:"aud,omitempty"`
ExpiresAt int64 `json:"exp,omitempty"`
Id string `json:"jti,omitempty"`
IssuedAt int64 `json:"iat,omitempty"`
Issuer string `json:"iss,omitempty"`
NotBefore int64 `json:"nbf,omitempty"`
Subject string `json:"sub,omitempty"`
}
jwt官方規(guī)定的一些預(yù)定義的payload:
- iss (issuer):簽發(fā)人
- exp (expiration time):過(guò)期時(shí)間
- sub (subject):主題
- aud (audience):受眾
- nbf (Not Before):生效時(shí)間
- iat (Issued At):簽發(fā)時(shí)間
- jti (JWT ID):編號(hào)
7.Token
type Token struct {
Raw string // The raw token. Populated when you Parse a token
Method SigningMethod // The signing method used or to be used
Header map[string]interface{} // The first segment of the token
Claims Claims // The second segment of the token
Signature string // The third segment of the token. Populated when you Parse a token
Valid bool // Is the token valid? Populated when you Parse/Verify a token
}
Token的結(jié)構(gòu)體
8.ValidationError
type ValidationError struct {
Inner error // stores the error returned by external dependencies, i.e.: KeyFunc
Errors uint32 // bitfield. see ValidationError... constants
// contains filtered or unexported fields
}
定義解析Token時(shí)遇到的一些錯(cuò)誤
基本用法
創(chuàng)建一個(gè)token
type MyCustomClaims struct{
Username string `json:"username"`
jwt.StandardClaims
}
secretKey := []byte("Helloworld")
// 直接創(chuàng)建一個(gè)token對(duì)象,加密方式為HS256
// 下面的代碼等于
// token := NewWithClaims(jwt.SigningMethodHS256,MyCustomClaims{"Mike"})
token := New(jwt.SigningMethodHS256)
claims := MyCustomClaims{
"Mike",
}
token.Claims = claims
// 獲得最終的tokenStr
tokenStr, err := token.SignedString(secretKey)
解析一個(gè)token
var tokenString = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIiLCJleHAiOjE1MDAwLCJpc3MiOiJ0ZXN0In0.HE7fK0xOQwFEr4WDgRWj4teRPZ6i3GLwD5YCm6Pwu_c"
token, err := jwt.Parse(tokenString,func(token *jwt.Token)(interface{},error){
return []byte("Helloworld"), nil
})
// 檢查token是否合法
if token.Valid {
fmt.Println("token合法")
} else {
fmt.Println("token不合法, err:",err)
}
源碼分析
首先我們先來(lái)看Parse()
func (p *Parser) Parse(tokenString string, keyFunc Keyfunc) (*Token, error) {
return p.ParseWithClaims(tokenString, MapClaims{}, keyFunc)
}
實(shí)際上是調(diào)用了ParseWithClaims,第二個(gè)參數(shù)就是一個(gè)map[string]interface,這個(gè)函數(shù)的源碼如下,這個(gè)函數(shù)內(nèi)部調(diào)用的ParseUnverified,我們先來(lái)看看這個(gè)函數(shù)
官方的解釋是震蒋,這個(gè)函數(shù)不校驗(yàn)簽名的有效性茸塞,只單純負(fù)責(zé)把tokenStr變成Token對(duì)象查剖,而之后的事情就是交給ParseWithClaims來(lái)做啦
// WARNING: Don't use this method unless you know what you're doing
//
// This method parses the token but doesn't validate the signature. It's only
// ever useful in cases where you know the signature is valid (because it has
// been checked previously in the stack) and you want to extract values from
// it.
func (p *Parser) ParseUnverified(tokenString string, claims Claims) (token *Token, parts []string, err error) {
// 將tokenStr的三部分分開(kāi),如果不是三部分則報(bào)錯(cuò):不是規(guī)范的token
parts = strings.Split(tokenString, ".")
if len(parts) != 3 {
return nil, parts, NewValidationError("token contains an invalid number of segments", ValidationErrorMalformed)
}
// 將token的Raw寫(xiě)進(jìn)去
token = &Token{Raw: tokenString}
// parse Header
// 解析頭部
// 這里的DecodeSegment是base64url編碼的譯碼
var headerBytes []byte
if headerBytes, err = DecodeSegment(parts[0]); err != nil {
// 傳進(jìn)來(lái)的token不應(yīng)該包含bearer
if strings.HasPrefix(strings.ToLower(tokenString), "bearer ") {
return token, parts, NewValidationError("tokenstring should not contain 'bearer '", ValidationErrorMalformed)
}
return token, parts, &ValidationError{Inner: err, Errors: ValidationErrorMalformed}
}
// 將數(shù)據(jù)解碼到Header中,Header是一個(gè)map[string]interface{}
if err = json.Unmarshal(headerBytes, &token.Header); err != nil {
return token, parts, &ValidationError{Inner: err, Errors: ValidationErrorMalformed}
}
// parse Claims
// 解析Claims
var claimBytes []byte
token.Claims = claims
// Base64url譯碼
if claimBytes, err = DecodeSegment(parts[1]); err != nil {
return token, parts, &ValidationError{Inner: err, Errors: ValidationErrorMalformed}
}
// json解碼
dec := json.NewDecoder(bytes.NewBuffer(claimBytes))
if p.UseJSONNumber {
dec.UseNumber()
}
// JSON Decode. Special case for map type to avoid weird pointer behavior
// 這里傳進(jìn)來(lái)的claims是一個(gè)自定義結(jié)構(gòu)體的引用,所以decode到claims里面笋庄,token.Claims里面也有數(shù)據(jù)了
if c, ok := token.Claims.(MapClaims); ok {
err = dec.Decode(&c)
} else {
err = dec.Decode(&claims)
}
// Handle decode error
if err != nil {
return token, parts, &ValidationError{Inner: err, Errors: ValidationErrorMalformed}
}
// Lookup signature method
// 從Header里面取出alg,獲得SigningMethod
if method, ok := token.Header["alg"].(string); ok {
if token.Method = GetSigningMethod(method); token.Method == nil {
return token, parts, NewValidationError("signing method (alg) is unavailable.", ValidationErrorUnverifiable)
}
} else {
return token, parts, NewValidationError("signing method (alg) is unspecified.", ValidationErrorUnverifiable)
}
return token, parts, nil
}
可以看到效扫,ParseUnverified這個(gè)方法真的只是單純的解碼Header段和Claim段,然后檢查一下用的alg是不是合法菌仁,就返回了浩习,讓我們繼續(xù)往下看驗(yàn)證的邏輯
func (p *Parser) ParseWithClaims(tokenString string, claims Claims, keyFunc Keyfunc) (*Token, error) {
// 這里我們得到了未驗(yàn)證的token對(duì)象
token, parts, err := p.ParseUnverified(tokenString, claims)
if err != nil {
return token, err
}
// Verify signing method is in the required set
// 驗(yàn)證SigningMethod,如果提供了VaildMethods字段瘦锹,只有這幾種SigningMethod才被認(rèn)為是合法的
if p.ValidMethods != nil {
var signingMethodValid = false
var alg = token.Method.Alg()
for _, m := range p.ValidMethods {
if m == alg {
signingMethodValid = true
break
}
}
if !signingMethodValid {
// signing method is not in the listed set
return token, NewValidationError(fmt.Sprintf("signing method %v is invalid", alg), ValidationErrorSignatureInvalid)
}
}
// Lookup key
// 檢查KeyFunc,這里它回去檢查KeyFunc(token)的err,可是我看網(wǎng)上的教程都是直接返回SecretKey, nil的
// 我不是很理解這段代碼的意思
var key interface{}
if keyFunc == nil {
// keyFunc was not provided. short circuiting validation
return token, NewValidationError("no Keyfunc was provided.", ValidationErrorUnverifiable)
}
if key, err = keyFunc(token); err != nil {
// keyFunc returned an error
if ve, ok := err.(*ValidationError); ok {
return token, ve
}
return token, &ValidationError{Inner: err, Errors: ValidationErrorUnverifiable}
}
vErr := &ValidationError{}
// Validate Claims
// 驗(yàn)證Claims段,如果SkipClaimsValidation為false
if !p.SkipClaimsValidation {
// Claims是一個(gè)實(shí)現(xiàn)了Vaild方法的接口,所以直接調(diào)用token.Claims的Valid方法
// jwt.Standardclaims實(shí)現(xiàn)了Claims結(jié)構(gòu)
if err := token.Claims.Valid(); err != nil {
// If the Claims Valid returned an error, check if it is a validation error,
// If it was another error type, create a ValidationError with a generic ClaimsInvalid flag set
if e, ok := err.(*ValidationError); !ok {
vErr = &ValidationError{Inner: err, Errors: ValidationErrorClaimsInvalid}
} else {
vErr = e
}
}
}
// Perform validation
// 這里是調(diào)用SigningMethod結(jié)構(gòu)的Verify方法闪盔,檢查token是否有效
token.Signature = parts[2]
if err = token.Method.Verify(strings.Join(parts[0:2], "."), token.Signature, key); err != nil {
vErr.Inner = err
vErr.Errors |= ValidationErrorSignatureInvalid
}
// vaild就是看vErr.Error是否為0,這里面記錄著驗(yàn)證token時(shí)可能出現(xiàn)的一些錯(cuò)誤
if vErr.valid() {
token.Valid = true
return token, nil
}
return token, vErr
}
ok,關(guān)于解析token的主要方法我們已經(jīng)看完了泪掀,接下來(lái)我們來(lái)看看如何生成一個(gè)token听绳,其實(shí)就是反著操作一遍
先看New函數(shù),選擇一種SigningMethod异赫,新建一個(gè)token,內(nèi)部調(diào)用NewWithClaims
// Create a new Token. Takes a signing method
func New(method SigningMethod) *Token {
return NewWithClaims(method, MapClaims{})
}
再看NewWithClaims,發(fā)現(xiàn)就是簡(jiǎn)單的給JwtToken的三個(gè)部分賦值
func NewWithClaims(method SigningMethod, claims Claims) *Token {
return &Token{
Header: map[string]interface{}{
"typ": "JWT",
"alg": method.Alg(),
},
Claims: claims,
Method: method,
}
}
最后是SignedString塔拳,即使用alg的算法給token加密鼠证,生成最終的tokenStr,內(nèi)部調(diào)用了SigningString靠抑,所以先看SigningString
發(fā)現(xiàn)SigningString就是把token的頭部先變成json然后base64url編碼量九,但是沒(méi)有生成jwtToken的最后一個(gè)部分
// Generate the signing string. This is the
// most expensive part of the whole deal. Unless you
// need this for something special, just go straight for
// the SignedString.
func (t *Token) SigningString() (string, error) {
var err error
parts := make([]string, 2)
for i, _ := range parts {
var jsonValue []byte
if i == 0 {
if jsonValue, err = json.Marshal(t.Header); err != nil {
return "", err
}
} else {
if jsonValue, err = json.Marshal(t.Claims); err != nil {
return "", err
}
}
parts[i] = EncodeSegment(jsonValue)
}
return strings.Join(parts, "."), nil
}
所以SignedString作用就是用給定的加密方法和你的SecretKey對(duì)前面兩部分加密颂碧,添在token的最后一段荠列,至此token生成完畢
// Get the complete, signed token
func (t *Token) SignedString(key interface{}) (string, error) {
var sig, sstr string
var err error
if sstr, err = t.SigningString(); err != nil {
return "", err
}
if sig, err = t.Method.Sign(sstr, key); err != nil {
return "", err
}
return strings.Join([]string{sstr, sig}, "."), nil
}