jwt-go库介绍

jwt-go笔记

这个库已经没人维护了,他们现在新搞了一个,具体可以去github上看

jwt介绍

jwt(json web token)是一种用于前后端身份认证的方法,一个jwt由header,payload,和signature组成。

  • header:包含了token类型和算法类型
  • payload:包含了一些用户自定义或jwt预定义的一些数据,每一个数据叫一个claim,注意不要将敏感信息放入
  • signature:将header和payload经过base64编码,加上一个secret密钥,整体经过header中的算法加密后生成

jwt-go重要的几个结构

1.Claims

type Claims interface {
    Valid() error
}

claims是一个实现了Valid方法的interface,Valid方法用于判断该claim是否合法

2.Keyfunc

type Keyfunc func(*Token) (interface{}, error)

Keyfunc在使用时一般都是返回secret密钥,可以根据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{}

一个用于放decode出来的claim的map,有Vaild和一系列VerifyXXX的方法

4.Parser

type Parser struct {
    ValidMethods         []string // 有效的加密方法列表,如果不为空,则Parse.Method.Alg()必需是VaildMethods的一种,否则报错
    UseJSONNumber        bool     // Use JSON Number format in JSON decoder
    SkipClaimsValidation bool     // 在解析token时跳过claims的验证
}

用来将tokenstr转换成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')
}

签名方法的接口,可以通过实现这个接口自定义签名方法,jwt-go内置一些实现了SigningMethod的结构体

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官方规定的一些预定义的payload:

  • iss (issuer):签发人
  • exp (expiration time):过期时间
  • sub (subject):主题
  • aud (audience):受众
  • nbf (Not Before):生效时间
  • iat (Issued At):签发时间
  • jti (JWT ID):编号

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的结构体

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时遇到的一些错误

基本用法

创建一个token

type MyCustomClaims struct{
    Username string `json:"username"`
    jwt.StandardClaims
}

secretKey := []byte("Helloworld")

// 直接创建一个token对象,加密方式为HS256
// 下面的代码等于
// token :=  NewWithClaims(jwt.SigningMethodHS256,MyCustomClaims{"Mike"})
token := New(jwt.SigningMethodHS256)
claims := MyCustomClaims{
    "Mike",
}
token.Claims = claims

// 获得最终的tokenStr
tokenStr, err := token.SignedString(secretKey)
    

解析一个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)
}

源码分析

首先我们先来看Parse()

func (p *Parser) Parse(tokenString string, keyFunc Keyfunc) (*Token, error) {
    return p.ParseWithClaims(tokenString, MapClaims{}, keyFunc)
}

实际上是调用了ParseWithClaims,第二个参数就是一个map[string]interface,这个函数的源码如下,这个函数内部调用的ParseUnverified,我们先来看看这个函数
官方的解释是,这个函数不校验签名的有效性,只单纯负责把tokenStr变成Token对象,而之后的事情就是交给ParseWithClaims来做啦

// 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的三部分分开,如果不是三部分则报错:不是规范的token
    parts = strings.Split(tokenString, ".")
    if len(parts) != 3 {
        return nil, parts, NewValidationError("token contains an invalid number of segments", ValidationErrorMalformed)
    }
    // 将token的Raw写进去
    token = &Token{Raw: tokenString}

    // parse Header
    // 解析头部
    // 这里的DecodeSegment是base64url编码的译码
    var headerBytes []byte
    if headerBytes, err = DecodeSegment(parts[0]); err != nil {
        // 传进来的token不应该包含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}
    }
    // 将数据解码到Header中,Header是一个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
    // 这里传进来的claims是一个自定义结构体的引用,所以decode到claims里面,token.Claims里面也有数据了
    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这个方法真的只是单纯的解码Header段和Claim段,然后检查一下用的alg是不是合法,就返回了,让我们继续往下看验证的逻辑

func (p *Parser) ParseWithClaims(tokenString string, claims Claims, keyFunc Keyfunc) (*Token, error) {
    
    // 这里我们得到了未验证的token对象
    token, parts, err := p.ParseUnverified(tokenString, claims)
    if err != nil {
        return token, err
    }

    
    // Verify signing method is in the required set
    // 验证SigningMethod,如果提供了VaildMethods字段,只有这几种SigningMethod才被认为是合法的
    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,可是我看网上的教程都是直接返回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
    // 验证Claims段,如果SkipClaimsValidation为false
    if !p.SkipClaimsValidation {
        // Claims是一个实现了Vaild方法的接口,所以直接调用token.Claims的Valid方法
        // jwt.Standardclaims实现了Claims结构
        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
    // 这里是调用SigningMethod结构的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,这里面记录着验证token时可能出现的一些错误
    if vErr.valid() {
        token.Valid = true
        return token, nil
    }

    return token, vErr
}

ok,关于解析token的主要方法我们已经看完了,接下来我们来看看如何生成一个token,其实就是反着操作一遍
先看New函数,选择一种SigningMethod,新建一个token,内部调用NewWithClaims

// Create a new Token.  Takes a signing method
func New(method SigningMethod) *Token {
    return NewWithClaims(method, MapClaims{})
}

再看NewWithClaims,发现就是简单的给JwtToken的三个部分赋值

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,内部调用了SigningString,所以先看SigningString
发现SigningString就是把token的头部先变成json然后base64url编码,但是没有生成jwtToken的最后一个部分

// 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对前面两部分加密,添在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
}

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,525评论 6 507
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,203评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,862评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,728评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,743评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,590评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,330评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,244评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,693评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,885评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,001评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,723评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,343评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,919评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,042评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,191评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,955评论 2 355

推荐阅读更多精彩内容