天天看點

JWT實作使用者認證原理與實作(golang)

1 JWT标準規範

JWT(JSON Web Token)是一個非常輕巧的規範。這個規範允許我們使用JWT在使用者和伺服器之間傳遞安全可靠的資訊。

一個JWT由三部分組成,頭部、載荷與簽名。

JWT原理類似我們加蓋公章或手寫簽名的的過程,合同上寫了很多條款,不是随便一張紙随便寫啥都可以的,必須要一些證明,比如簽名,比如蓋章。JWT就是通過附加簽名,保證傳輸過來的資訊是真的,而不是僞造的。

頭部:

用于說明簽名的加密算法等,下面類型的json經過base64編碼後得到JWT頭部

{
  "typ": "JWT",
  "alg": "HS256"
}
           

載荷:

包含生成Token時間,過期時間,以及一些身份辨別,标準定義了6個字段,載荷json經過base64編碼後得到JWT的載荷:

sub: 該JWT所面向的使用者

iss: 該JWT的簽發者

iat(issued at): 在什麼時候簽發的token

exp(expires): token什麼時候過期

nbf(not before):token在此時間之前不能被接收處理

jti:JWT ID為web token提供唯一辨別

例子:

{
    "sub": "1",
    "iss": "http://localhost:8000/user/sign_up",
    "iat": 1451888119,
    "exp": 1454516119,
    "nbf": 1451888119,
    "jti": "37c107e4609ddbcc9c096ea5ee76c667"
}
           

簽名:

将頭部和載荷用'.'号連接配接,再加上一串密鑰,經過頭部聲明的加密算法加密後得到簽名

HMACSHA256(
    base64UrlEncode(header) + "." +
    base64UrlEncode(payload),
    secret
)
           

JWT Token

Token=頭部+'.'+載荷+'.'+簽名

2 代碼實作

實作代碼來之開源項目https://github.com/dgrijalva/jwt-go

使用例子

Token生成:

var(
    key []byte = []byte("Hello World!This is secret!")
)
// 産生json web token
func GenToken() string {
    claims := &jwt.StandardClaims{
        NotBefore: int64(time.Now().Unix()),
        ExpiresAt: int64(time.Now().Unix() + 1000),
        Issuer:    "Bitch",
    }
 
    token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
    ss, err := token.SignedString(key)
    if err != nil {
        logs.Error(err)
        return ""
    }
    return ss
}
           

檢驗Token:

// 校驗token是否有效

func CheckToken(token string) bool {
    _, err := jwt.Parse(token, func(*jwt.Token) (interface{}, error) {
        return key, nil
    })
    if err != nil {
        fmt.Println("parase with claims failed.", err)
        return false
    }
    return true
}
           

基本資料結構

上面是簽名的使用例子,下面分析簽名的源碼實作,首先看下資料結構Token,包含了我們标準中說道的三部分:頭部,載荷和簽名,此外還帶了一個用于存儲生成token的字段Raw和校驗辨別符Valid

// A JWT Token.  Different fields will be used depending on whether you're
// creating or parsing/verifying a 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
}
           

看下載下傳荷,多了一個字段Audience

// Structured version of Claims Section, as referenced at
// https://tools.ietf.org/html/rfc7519#section-4.1
// See examples for how to use this with your own claim types
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"`
}
           

API介紹

初始化API

提供了兩個API,一個隻需提供加密方法,一個需要提供加密方法和載荷結構對象

// Create a new Token.  Takes a signing method
func New(method SigningMethod) *Token {
    return NewWithClaims(method, MapClaims{})
}
 
func NewWithClaims(method SigningMethod, claims Claims) *Token {
    return &Token{
        Header: map[string]interface{}{
            "typ": "JWT",
            "alg": method.Alg(),
        },
        Claims: claims,
        Method: method,
    }
}
           

生成簽名API

如标準所說,主要是将頭部和載荷部分的結構對象轉化為json格式,然後用base64編碼,然後用'.'号連接配接,然後使用指定加密方法生成簽名,再與前面的頭部和載荷用'.'号連接配接

// Get the complete, signed token
func (t *Token) SignedString(key interface{}) (string, error) {
    var sig, sstr string
    var err error
    //把頭部和載荷轉化為json格式,base64編碼之後用'.'号連接配接起來
    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
}
           

頭部和載荷資料結構對象的處理:

// 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
}
           

校驗API

檢驗Token基本就是生成Token的逆過程了,也提供了兩個API:

// Parse, validate, and return a token.
// keyFunc will receive the parsed token and should return the key for validating.
// If everything is kosher, err will be nil
func Parse(tokenString string, keyFunc Keyfunc) (*Token, error) {
    return new(Parser).Parse(tokenString, keyFunc)
}
 
func ParseWithClaims(tokenString string, claims Claims, keyFunc Keyfunc) (*Token, error) {
    return new(Parser).ParseWithClaims(tokenString, claims, keyFunc)
}
           

--------------------- 

作者:idwtwt 

來源:CSDN 

原文:https://blog.csdn.net/idwtwt/article/details/80865209 

版權聲明:本文為部落客原創文章,轉載請附上博文連結!