1. 程式人生 > >Go實戰--golang中使用JWT(JSON Web Token)

Go實戰--golang中使用JWT(JSON Web Token)

今天就來跟大家簡單介紹一下golang中如何使用token,當然是要依賴一下github上的優秀的開源庫了。

首先,要搞明白一個問題,token、cookie、session的區別。

token、cookie、session的區別
Cookie 
Cookie總是儲存在客戶端中,按在客戶端中的儲存位置,可分為記憶體Cookie和硬碟Cookie。

記憶體Cookie由瀏覽器維護,儲存在記憶體中,瀏覽器關閉後就消失了,其存在時間是短暫的。硬碟Cookie儲存在硬盤裡,有一個過期時間,除非使用者手工清理或到了過期時間,硬碟Cookie不會被刪除,其存在時間是長期的。所以,按存在時間,可分為非持久Cookie和持久Cookie。

cookie 是一個非常具體的東西,指的就是瀏覽器裡面能永久儲存的一種資料,僅僅是瀏覽器實現的一種資料儲存功能。

cookie由伺服器生成,傳送給瀏覽器,瀏覽器把cookie以key-value形式儲存到某個目錄下的文字檔案內,下一次請求同一網站時會把該cookie傳送給伺服器。由於cookie是存在客戶端上的,所以瀏覽器加入了一些限制確保cookie不會被惡意使用,同時不會佔據太多磁碟空間,所以每個域的cookie數量是有限的。

Session

session 從字面上講,就是會話。這個就類似於你和一個人交談,你怎麼知道當前和你交談的是張三而不是李四呢?對方肯定有某種特徵(長相等)表明他就是張三。

session 也是類似的道理,伺服器要知道當前發請求給自己的是誰。為了做這種區分,伺服器就要給每個客戶端分配不同的“身份標識”,然後客戶端每次向伺服器發請求的時候,都帶上這個“身份標識”,伺服器就知道這個請求來自於誰了。至於客戶端怎麼儲存這個“身份標識”,可以有很多種方式,對於瀏覽器客戶端,大家都預設採用 cookie 的方式。

伺服器使用session把使用者的資訊臨時儲存在了伺服器上,使用者離開網站後session會被銷燬。這種使用者資訊儲存方式相對cookie來說更安全,可是session有一個缺陷:如果web伺服器做了負載均衡,那麼下一個操作請求到了另一臺伺服器的時候session會丟失。

Token 
token的意思是“令牌”,是使用者身份的驗證方式,最簡單的token組成:uid(使用者唯一的身份標識)、time(當前時間的時間戳)、sign(簽名,由token的前幾位+鹽以雜湊演算法壓縮成一定長的十六進位制字串,可以防止惡意第三方拼接token請求伺服器)。還可以把不變的引數也放進token,避免多次查庫

這裡的token是指SON Web Token: 
JSON Web Token (JWT) is a compact URL-safe means of representing claims to be transferred between two parties. The claims in a JWT are encoded as a JSON object that is digitally signed using JSON Web Signature (JWS).

使用JWT進行認證 
JSON Web Tokens (JWT) are a more modern approach to authentication.

As the web moves to a greater separation between the client and server, JWT provides a wonderful alternative to traditional cookie based authentication models.

JWTs provide a way for clients to authenticate every request without having to maintain a session or repeatedly pass login credentials to the server.

使用者註冊之後, 伺服器生成一個 JWT token返回給瀏覽器, 瀏覽器向伺服器請求資料時將 JWT token 發給伺服器, 伺服器用 signature 中定義的方式解碼 
JWT 獲取使用者資訊.

一個 JWT token包含3部分: 
1. header: 告訴我們使用的演算法和 token 型別 
2. Payload: 必須使用 sub key 來指定使用者 ID, 還可以包括其他資訊比如 email, username 等. 
3. Signature: 用來保證 JWT 的真實性. 可以使用不同演算法 


JWT應用
上面說了那麼多,接下來就是要coding了。 
用到的開源庫: 
github.com/codegangsta/negroni 
Idiomatic HTTP Middleware for Golang 
http的一箇中間件

github.com/dgrijalva/jwt-go 
Golang implementation of JSON Web Tokens (JWT)

github.com/dgrijalva/jwt-go/request

這裡分兩個api,一個是通過login獲取token,然後根據token訪問另一個api。首先看看login是如何生成token的: 
當然首先是驗證使用者名稱和密碼,為了節省篇幅這裡只是程式碼片段,完整程式碼最後獻上。

    token := jwt.New(jwt.SigningMethodHS256)
    claims := make(jwt.MapClaims)
    claims["exp"] = time.Now().Add(time.Hour * time.Duration(1)).Unix()
    claims["iat"] = time.Now().Unix()
    token.Claims = claims

    if err != nil {
        w.WriteHeader(http.StatusInternalServerError)
        fmt.Fprintln(w, "Error extracting the key")
        fatal(err)
    }

    tokenString, err := token.SignedString([]byte(SecretKey))
    if err != nil {
        w.WriteHeader(http.StatusInternalServerError)
        fmt.Fprintln(w, "Error while signing the token")
        fatal(err)
    }


接下來就是驗證token的中介軟體了:

    token, err := request.ParseFromRequest(r, request.AuthorizationHeaderExtractor,
        func(token *jwt.Token) (interface{}, error) {
            return []byte(SecretKey), nil
        })

    if err == nil {
        if token.Valid {
            next(w, r)
        } else {
            w.WriteHeader(http.StatusUnauthorized)
            fmt.Fprint(w, "Token is not valid")
        }
    } else {
        w.WriteHeader(http.StatusUnauthorized)
        fmt.Fprint(w, "Unauthorized access to this resource")
    }


最後完整程式碼:

package main

import (
    "encoding/json"
    "fmt"
    "log"
    "net/http"
    "strings"
    "time"

    "github.com/codegangsta/negroni"
    "github.com/dgrijalva/jwt-go"
    "github.com/dgrijalva/jwt-go/request"
)

const (
    SecretKey = "welcome to wangshubo's blog"
)

func fatal(err error) {
    if err != nil {
        log.Fatal(err)
    }
}

type UserCredentials struct {
    Username string `json:"username"`
    Password string `json:"password"`
}

type User struct {
    ID       int    `json:"id"`
    Name     string `json:"name"`
    Username string `json:"username"`
    Password string `json:"password"`
}

type Response struct {
    Data string `json:"data"`
}

type Token struct {
    Token string `json:"token"`
}

func StartServer() {

    http.HandleFunc("/login", LoginHandler)

    http.Handle("/resource", negroni.New(
        negroni.HandlerFunc(ValidateTokenMiddleware),
        negroni.Wrap(http.HandlerFunc(ProtectedHandler)),
    ))

    log.Println("Now listening...")
    http.ListenAndServe(":8080", nil)
}

func main() {
    StartServer()
}

func ProtectedHandler(w http.ResponseWriter, r *http.Request) {

    response := Response{"Gained access to protected resource"}
    JsonResponse(response, w)

}

func LoginHandler(w http.ResponseWriter, r *http.Request) {

    var user UserCredentials

    err := json.NewDecoder(r.Body).Decode(&user)

    if err != nil {
        w.WriteHeader(http.StatusForbidden)
        fmt.Fprint(w, "Error in request")
        return
    }

    if strings.ToLower(user.Username) != "someone" {
        if user.Password != "[email protected]" {
            w.WriteHeader(http.StatusForbidden)
            fmt.Println("Error logging in")
            fmt.Fprint(w, "Invalid credentials")
            return
        }
    }

    token := jwt.New(jwt.SigningMethodHS256)
    claims := make(jwt.MapClaims)
    claims["exp"] = time.Now().Add(time.Hour * time.Duration(1)).Unix()
    claims["iat"] = time.Now().Unix()
    token.Claims = claims

    if err != nil {
        w.WriteHeader(http.StatusInternalServerError)
        fmt.Fprintln(w, "Error extracting the key")
        fatal(err)
    }

    tokenString, err := token.SignedString([]byte(SecretKey))
    if err != nil {
        w.WriteHeader(http.StatusInternalServerError)
        fmt.Fprintln(w, "Error while signing the token")
        fatal(err)
    }

    response := Token{tokenString}
    JsonResponse(response, w)

}

func ValidateTokenMiddleware(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {

    token, err := request.ParseFromRequest(r, request.AuthorizationHeaderExtractor,
        func(token *jwt.Token) (interface{}, error) {
            return []byte(SecretKey), nil
        })

    if err == nil {
        if token.Valid {
            next(w, r)
        } else {
            w.WriteHeader(http.StatusUnauthorized)
            fmt.Fprint(w, "Token is not valid")
        }
    } else {
        w.WriteHeader(http.StatusUnauthorized)
        fmt.Fprint(w, "Unauthorized access to this resource")
    }

}

func JsonResponse(response interface{}, w http.ResponseWriter) {

    json, err := json.Marshal(response)
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }

    w.WriteHeader(http.StatusOK)
    w.Header().Set("Content-Type", "application/json")
    w.Write(json)
}


通過postman進行驗證: 
login: 
這裡寫圖片描述

根據獲得token進行get請求: 
這裡寫圖片描述

這裡寫圖片描述

 

--------------------- 
作者:一蓑煙雨1989 
來源:CSDN 
原文:https://blog.csdn.net/wangshubo1989/article/details/74529333 
版權宣告:本文為博主原創文章,轉載請附上博文連結!