原文地址: 使用JWT進行身份校驗
在前面幾節中,咱們已經基本的完成了API's的編寫html
可是,還存在一些很是嚴重的問題,例如,咱們如今的API是能夠隨意調用的,這顯然還不夠完美,是有問題的git
那麼咱們採用 jwt-go (GoDoc)的方式來簡單解決這個問題github
項目地址:https://github.com/EDDYCJY/go...golang
首先,咱們下載jwt-go的依賴包json
go get -u github.com/dgrijalva/jwt-go
jwt
工具包咱們須要編寫一個jwt
的工具包,咱們在pkg
下的util
目錄新建jwt.go
,寫入文件內容:segmentfault
package util import ( "time" jwt "github.com/dgrijalva/jwt-go" "gin-blog/pkg/setting" ) var jwtSecret = []byte(setting.JwtSecret) type Claims struct { Username string `json:"username"` Password string `json:"password"` jwt.StandardClaims } func GenerateToken(username, password string) (string, error) { nowTime := time.Now() expireTime := nowTime.Add(3 * time.Hour) claims := Claims{ username, password, jwt.StandardClaims { ExpiresAt : expireTime.Unix(), Issuer : "gin-blog", }, } tokenClaims := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) token, err := tokenClaims.SignedString(jwtSecret) return token, err } func ParseToken(token string) (*Claims, error) { tokenClaims, err := jwt.ParseWithClaims(token, &Claims{}, func(token *jwt.Token) (interface{}, error) { return jwtSecret, nil }) if tokenClaims != nil { if claims, ok := tokenClaims.Claims.(*Claims); ok && tokenClaims.Valid { return claims, nil } } return nil, err }
在這個工具包,咱們涉及到api
NewWithClaims(method SigningMethod, claims Claims)
,method
對應着SigningMethodHMAC struct{}
,其包含SigningMethodHS256
、SigningMethodHS384
、SigningMethodHS512
三種crypto.Hash
方案func (t *Token) SignedString(key interface{})
該方法內部生成簽名字符串,再用於獲取完整、已簽名的token
func (p *Parser) ParseWithClaims
用於解析鑑權的聲明,方法內部主要是具體的解碼和校驗的過程,最終返回*Token
func (m MapClaims) Valid()
驗證基於時間的聲明exp, iat, nbf
,注意若是沒有任何聲明在令牌中,仍然會被認爲是有效的。而且對於時區誤差沒有計算方法有了jwt
工具包,接下來咱們要編寫要用於Gin
的中間件,咱們在middleware
下新建jwt
目錄,新建jwt.go
文件,寫入內容:緩存
package jwt import ( "time" "net/http" "github.com/gin-gonic/gin" "gin-blog/pkg/util" "gin-blog/pkg/e" ) func JWT() gin.HandlerFunc { return func(c *gin.Context) { var code int var data interface{} code = e.SUCCESS token := c.Query("token") if token == "" { code = e.INVALID_PARAMS } else { claims, err := util.ParseToken(token) if err != nil { code = e.ERROR_AUTH_CHECK_TOKEN_FAIL } else if time.Now().Unix() > claims.ExpiresAt { code = e.ERROR_AUTH_CHECK_TOKEN_TIMEOUT } } if code != e.SUCCESS { c.JSON(http.StatusUnauthorized, gin.H{ "code" : code, "msg" : e.GetMsg(code), "data" : data, }) c.Abort() return } c.Next() } }
Token
那麼咱們如何調用它呢,咱們還要獲取Token
呢?app
一、 咱們要新增一個獲取Token
的API工具
在models
下新建auth.go
文件,寫入內容:
package models type Auth struct { ID int `gorm:"primary_key" json:"id"` Username string `json:"username"` Password string `json:"password"` } func CheckAuth(username, password string) bool { var auth Auth db.Select("id").Where(Auth{Username : username, Password : password}).First(&auth) if auth.ID > 0 { return true } return false }
在routers
下的api
目錄新建auth.go
文件,寫入內容:
package api import ( "log" "net/http" "github.com/gin-gonic/gin" "github.com/astaxie/beego/validation" "gin-blog/pkg/e" "gin-blog/pkg/util" "gin-blog/models" ) type auth struct { Username string `valid:"Required; MaxSize(50)"` Password string `valid:"Required; MaxSize(50)"` } func GetAuth(c *gin.Context) { username := c.Query("username") password := c.Query("password") valid := validation.Validation{} a := auth{Username: username, Password: password} ok, _ := valid.Valid(&a) data := make(map[string]interface{}) code := e.INVALID_PARAMS if ok { isExist := models.CheckAuth(username, password) if isExist { token, err := util.GenerateToken(username, password) if err != nil { code = e.ERROR_AUTH_TOKEN } else { data["token"] = token code = e.SUCCESS } } else { code = e.ERROR_AUTH } } else { for _, err := range valid.Errors { log.Println(err.Key, err.Message) } } c.JSON(http.StatusOK, gin.H{ "code" : code, "msg" : e.GetMsg(code), "data" : data, }) }
咱們打開routers
目錄下的router.go
文件,修改文件內容(新增獲取token的方法):
package routers import ( "github.com/gin-gonic/gin" "gin-blog/routers/api" "gin-blog/routers/api/v1" "gin-blog/pkg/setting" ) func InitRouter() *gin.Engine { r := gin.New() r.Use(gin.Logger()) r.Use(gin.Recovery()) gin.SetMode(setting.RunMode) r.GET("/auth", api.GetAuth) apiv1 := r.Group("/api/v1") { ... } return r }
Token
獲取token
的API方法就到這裏啦,讓咱們來測試下是否能夠正常使用吧!
重啓服務後,用GET
方式訪問http://127.0.0.1:8000/auth?username=test&password=test123456
,查看返回值是否正確
{ "code": 200, "data": { "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6InRlc3QiLCJwYXNzd29yZCI6InRlc3QxMjM0NTYiLCJleHAiOjE1MTg3MjAwMzcsImlzcyI6Imdpbi1ibG9nIn0.-kK0V9E06qTHOzupQM_gHXAGDB3EJtJS4H5TTCyWwW8" }, "msg": "ok" }
咱們有了token
的API,也調用成功了
Gin
二、 接下來咱們將中間件接入到Gin
的訪問流程中
咱們打開routers
目錄下的router.go
文件,修改文件內容(新增引用包和中間件引用)
package routers import ( "github.com/gin-gonic/gin" "gin-blog/routers/api" "gin-blog/routers/api/v1" "gin-blog/pkg/setting" "gin-blog/middleware/jwt" ) func InitRouter() *gin.Engine { r := gin.New() r.Use(gin.Logger()) r.Use(gin.Recovery()) gin.SetMode(setting.RunMode) r.GET("/auth", api.GetAuth) apiv1 := r.Group("/api/v1") apiv1.Use(jwt.JWT()) { ... } return r }
當前目錄結構:
gin-blog/ ├── conf │ └── app.ini ├── main.go ├── middleware │ └── jwt │ └── jwt.go ├── models │ ├── article.go │ ├── auth.go │ ├── models.go │ └── tag.go ├── pkg │ ├── e │ │ ├── code.go │ │ └── msg.go │ ├── setting │ │ └── setting.go │ └── util │ ├── jwt.go │ └── pagination.go ├── routers │ ├── api │ │ ├── auth.go │ │ └── v1 │ │ ├── article.go │ │ └── tag.go │ └── router.go ├── runtime
到這裏,咱們的JWT
編寫就完成啦!
咱們來測試一下,再次訪問
正確的反饋應該是
{ "code": 400, "data": null, "msg": "請求參數錯誤" } { "code": 20001, "data": null, "msg": "Token鑑權失敗" }
咱們須要訪問http://127.0.0.1:8000/auth?username=test&password=test123456
,獲得token
{ "code": 200, "data": { "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6InRlc3QiLCJwYXNzd29yZCI6InRlc3QxMjM0NTYiLCJleHAiOjE1MTg3MjQ2OTMsImlzcyI6Imdpbi1ibG9nIn0.KSBY6TeavV_30kfmP7HWLRYKP5TPEDgHtABe9HCsic4" }, "msg": "ok" }
再用包含token
的URL參數去訪問咱們的應用API,
訪問http://127.0.0.1:8000/api/v1/articles?token=eyJhbGci...
,檢查接口返回值
{ "code": 200, "data": { "lists": [ { "id": 2, "created_on": 1518700920, "modified_on": 0, "tag_id": 1, "tag": { "id": 1, "created_on": 1518684200, "modified_on": 0, "name": "tag1", "created_by": "", "modified_by": "", "state": 0 }, "content": "test-content", "created_by": "test-created", "modified_by": "", "state": 0 } ], "total": 1 }, "msg": "ok" }
返回正確,至此咱們的jwt-go
在Gin
中的驗證就完成了!