jwt-go庫(kù)介紹

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
}

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末载城,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子诉瓦,更是在濱河造成了極大的恐慌川队,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,126評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件固额,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡猴贰,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén)河狐,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)瑟捣,“玉大人,你說(shuō)我怎么就攤上這事栅干÷跆祝” “怎么了碱鳞?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,445評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵桑李,是天一觀的道長(zhǎng)窿给。 經(jīng)常有香客問(wèn)我贵白,道長(zhǎng)崩泡,這世上最難降的妖魔是什么禁荒? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,185評(píng)論 1 278
  • 正文 為了忘掉前任角撞,我火速辦了婚禮,結(jié)果婚禮上谒所,老公的妹妹穿的比我還像新娘热康。我一直安慰自己,他們只是感情好劣领,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,178評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著剖踊,像睡著了一般庶弃。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上德澈,一...
    開(kāi)封第一講書(shū)人閱讀 48,970評(píng)論 1 284
  • 那天,我揣著相機(jī)與錄音梆造,去河邊找鬼缴守。 笑死,一個(gè)胖子當(dāng)著我的面吹牛镇辉,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播忽肛,決...
    沈念sama閱讀 38,276評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼村砂,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了础废?” 一聲冷哼從身側(cè)響起汛骂,我...
    開(kāi)封第一講書(shū)人閱讀 36,927評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎评腺,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體蒿讥,經(jīng)...
    沈念sama閱讀 43,400評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡蝶念,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,883評(píng)論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了媒殉。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 37,997評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡侥钳,死狀恐怖适袜,靈堂內(nèi)的尸體忽然破棺而出舷夺,到底是詐尸還是另有隱情苦酱,我是刑警寧澤,帶...
    沈念sama閱讀 33,646評(píng)論 4 322
  • 正文 年R本政府宣布疫萤,位于F島的核電站,受9級(jí)特大地震影響敢伸,放射性物質(zhì)發(fā)生泄漏扯饶。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,213評(píng)論 3 307
  • 文/蒙蒙 一尾序、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧躯砰,春花似錦每币、人聲如沸琢歇。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,204評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)揭保。三九已至,卻和暖如春秸侣,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,423評(píng)論 1 260
  • 我被黑心中介騙來(lái)泰國(guó)打工味榛, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留方篮,地道東北人励负。 一個(gè)月前我還...
    沈念sama閱讀 45,423評(píng)論 2 352
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像继榆,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子汁掠,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,722評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容