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>