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

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

生命不止,繼續(xù) go go go !!!

之前寫過關(guān)于golang中如何使用cookie的博客:
實戰(zhàn)–go中使用cookie

今天就來跟大家簡單介紹一下golang中如何使用token,當(dāng)然是要依賴一下github上的優(yōu)秀的開源庫了庙睡。

首先诀艰,要搞明白一個問題,token拧篮、cookie史飞、session的區(qū)別。

token瓷翻、cookie礼仗、session的區(qū)別

Cookie
Cookie總是保存在客戶端中,按在客戶端中的存儲位置逻悠,可分為內(nèi)存Cookie和硬盤Cookie元践。

內(nèi)存Cookie由瀏覽器維護,保存在內(nèi)存中童谒,瀏覽器關(guān)閉后就消失了单旁,其存在時間是短暫的。硬盤Cookie保存在硬盤里饥伊,有一個過期時間象浑,除非用戶手工清理或到了過期時間,硬盤Cookie不會被刪除琅豆,其存在時間是長期的愉豺。所以,按存在時間茫因,可分為非持久Cookie和持久Cookie蚪拦。

cookie 是一個非常具體的東西,指的就是瀏覽器里面能永久存儲的一種數(shù)據(jù)冻押,僅僅是瀏覽器實現(xiàn)的一種數(shù)據(jù)存儲功能驰贷。

cookie由服務(wù)器生成,發(fā)送給瀏覽器洛巢,瀏覽器把cookie以key-value形式保存到某個目錄下的文本文件內(nèi)括袒,下一次請求同一網(wǎng)站時會把該cookie發(fā)送給服務(wù)器。由于cookie是存在客戶端上的稿茉,所以瀏覽器加入了一些限制確保cookie不會被惡意使用锹锰,同時不會占據(jù)太多磁盤空間芥炭,所以每個域的cookie數(shù)量是有限的。

Session

session 從字面上講恃慧,就是會話园蝠。這個就類似于你和一個人交談,你怎么知道當(dāng)前和你交談的是張三而不是李四呢糕伐?對方肯定有某種特征(長相等)表明他就是張三砰琢。

session 也是類似的道理蘸嘶,服務(wù)器要知道當(dāng)前發(fā)請求給自己的是誰良瞧。為了做這種區(qū)分,服務(wù)器就要給每個客戶端分配不同的“身份標(biāo)識”训唱,然后客戶端每次向服務(wù)器發(fā)請求的時候褥蚯,都帶上這個“身份標(biāo)識”,服務(wù)器就知道這個請求來自于誰了况增。至于客戶端怎么保存這個“身份標(biāo)識”赞庶,可以有很多種方式,對于瀏覽器客戶端澳骤,大家都默認采用 cookie 的方式歧强。

服務(wù)器使用session把用戶的信息臨時保存在了服務(wù)器上,用戶離開網(wǎng)站后session會被銷毀为肮。這種用戶信息存儲方式相對cookie來說更安全摊册,可是session有一個缺陷:如果web服務(wù)器做了負載均衡,那么下一個操作請求到了另一臺服務(wù)器的時候session會丟失颊艳。

Token
token的意思是“令牌”茅特,是用戶身份的驗證方式,最簡單的token組成:uid(用戶唯一的身份標(biāo)識)棋枕、time(當(dāng)前時間的時間戳)白修、sign(簽名,由token的前幾位+鹽以哈希算法壓縮成一定長的十六進制字符串重斑,可以防止惡意第三方拼接token請求服務(wù)器)兵睛。還可以把不變的參數(shù)也放進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.

用戶注冊之后, 服務(wù)器生成一個 JWT token返回給瀏覽器, 瀏覽器向服務(wù)器請求數(shù)據(jù)時將 JWT token 發(fā)給服務(wù)器, 服務(wù)器用 signature 中定義的方式解碼
JWT 獲取用戶信息.

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


JWT應(yīng)用

上面說了那么多窥浪,接下來就是要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寒矿,然后根據(jù)token訪問另一個api突琳。首先看看login是如何生成token的:
當(dāng)然首先是驗證用戶名和密碼,為了節(jié)省篇幅這里只是代碼片段符相,完整代碼最后獻上拆融。

    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 != "p@ssword" {
            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:


這里寫圖片描述

根據(jù)獲得token進行g(shù)et請求:


這里寫圖片描述

</article>

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末蠢琳,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子镜豹,更是在濱河造成了極大的恐慌傲须,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,284評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件趟脂,死亡現(xiàn)場離奇詭異泰讽,居然都是意外死亡,警方通過查閱死者的電腦和手機昔期,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,115評論 3 395
  • 文/潘曉璐 我一進店門已卸,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人硼一,你說我怎么就攤上這事累澡。” “怎么了般贼?”我有些...
    開封第一講書人閱讀 164,614評論 0 354
  • 文/不壞的土叔 我叫張陵愧哟,是天一觀的道長。 經(jīng)常有香客問我哼蛆,道長蕊梧,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,671評論 1 293
  • 正文 為了忘掉前任腮介,我火速辦了婚禮肥矢,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘萤厅。我一直安慰自己橄抹,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,699評論 6 392
  • 文/花漫 我一把揭開白布惕味。 她就那樣靜靜地躺著楼誓,像睡著了一般。 火紅的嫁衣襯著肌膚如雪名挥。 梳的紋絲不亂的頭發(fā)上疟羹,一...
    開封第一講書人閱讀 51,562評論 1 305
  • 那天,我揣著相機與錄音禀倔,去河邊找鬼榄融。 笑死,一個胖子當(dāng)著我的面吹牛救湖,可吹牛的內(nèi)容都是我干的愧杯。 我是一名探鬼主播,決...
    沈念sama閱讀 40,309評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼鞋既,長吁一口氣:“原來是場噩夢啊……” “哼力九!你這毒婦竟也來了耍铜?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,223評論 0 276
  • 序言:老撾萬榮一對情侶失蹤跌前,失蹤者是張志新(化名)和其女友劉穎棕兼,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體抵乓,經(jīng)...
    沈念sama閱讀 45,668評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡伴挚,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,859評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了灾炭。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片茎芋。...
    茶點故事閱讀 39,981評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖咆贬,靈堂內(nèi)的尸體忽然破棺而出败徊,到底是詐尸還是另有隱情帚呼,我是刑警寧澤掏缎,帶...
    沈念sama閱讀 35,705評論 5 347
  • 正文 年R本政府宣布,位于F島的核電站煤杀,受9級特大地震影響眷蜈,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜沈自,卻給世界環(huán)境...
    茶點故事閱讀 41,310評論 3 330
  • 文/蒙蒙 一酌儒、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧枯途,春花似錦忌怎、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,904評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至晚岭,卻和暖如春鸥印,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背坦报。 一陣腳步聲響...
    開封第一講書人閱讀 33,023評論 1 270
  • 我被黑心中介騙來泰國打工库说, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人片择。 一個月前我還...
    沈念sama閱讀 48,146評論 3 370
  • 正文 我出身青樓潜的,卻偏偏與公主長得像,于是被迫代替她去往敵國和親字管。 傳聞我的和親對象是個殘疾皇子啰挪,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,933評論 2 355

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