http://blog.csdn.net/wangshubo1989/article/details/74529333
之前寫過關於golang中如何使用cookie的博客:
實戰–go中使用cookie
今天就來跟大家簡單介紹一下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) }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
接下來就是驗證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") }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
最后完整代碼:
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) }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
- 132
- 133
- 134
- 135
- 136
- 137
- 138
- 139
- 140
- 141
- 142
- 143
- 144
- 145
- 146
- 147
通過postman進行驗證:
login: 
根據獲得token進行get請求: 
