這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
生命不止,繼續 go go go !!!
之前寫過關於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 來指定使用者識別碼, 還可以包括其他資訊比如 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 mainimport ( "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:
根據獲得token進行get請求: