【golang】iris的jwt實踐,獲取jwt、攜帶jwt、驗證jwt、設置過時時間、自定義錯誤處理函數、格式化錯誤返回

golang iris的jwt實踐

因爲jwt原理已經有不少文章說起過了,這裏再也不贅述,本文主要介紹jwt在iris中的實踐,文章的最後會給出完整代碼,能夠運行起來邊測試邊看。
若是文章對你有幫助,點個贊或者留下評論將會是對個人極大鼓勵!

jwt使用方向

本文將jwt用於登陸功能,若是是其餘功能需求其實也相似,能夠舉一反三。前端

iris中jwt的使用思想

iris基於中間件思想設計,對於一些重複性的操做,能夠經過註冊中間件來完成。對於登陸功能,咱們天然不但願每一個api中手動判斷是否過時,是否合法等,由於咱們須要一個jwt中間件來完成這些操做。git

獲取jwt中間件

這是一個iris官方提供的jwt中間件:github

import "github.com/iris-contrib/middleware/jwt"

用法以下:golang

j := jwt.New(jwt.Config{
    // Extractor屬性能夠選擇從什麼地方獲取jwt進行驗證,默認從http請求的header中的Authorization字段提取,也可指定爲請求參數中的某個字段

    // 從請求參數token中提取
    // Extractor: jwt.FromParameter("token"),

    // 從請求頭的Authorization字段中提取,這個是默認值
    Extractor: jwt.FromAuthHeader,

    // 設置一個函數返回祕鑰,關鍵在於return []byte("這裏設置祕鑰")
    ValidationKeyGetter: func(token *jwt.Token) (interface{}, error) {
      return []byte("My Secret"), nil
    },

    // 設置一個加密方法
    SigningMethod: jwt.SigningMethodHS256,
  })

jwt.Config中還有一些能夠設置的參數,可是便於理解,以以上三個參數做爲演示json

使用jwt中間件

因爲並不是全部接口都須要設置登陸攔截,因此將中間件用於路由,或者路由組是比較符合業務需求的作法後端

先給出兩個接口:api

  1. "/getJWT":生成jwt並返回
  2. "/showHello":沒有任何限制,訪問就輸出"Hello Iris JWT"
package main

import (
  "github.com/kataras/iris/v12"

  "github.com/iris-contrib/middleware/jwt"
)

func main() {
  app := iris.New()

  app.Get("/getJWT", func(ctx iris.Context) {
    // 往jwt中寫入了一對值
    token := jwt.NewTokenWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
      "foo": "bar",
    })

    // 簽名生成jwt字符串
    tokenString, _ := token.SignedString([]byte("My Secret"))

    // 返回
    ctx.JSON(tokenString)
  })

  app.Get("/showHello", func(ctx iris.Context) {
    ctx.JSON("Hello Iris JWT")
  })

  app.Run(iris.Addr(":8080"))
}

訪問http://localhost:8080/getJWT返回一串相似這樣的字符串app

"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIifQ.4aQan-XvJBjDUDbCVnuh2P_xy54b2aRKKsgKcHUa8uw"

訪問http://localhost:8080/showHello返回函數

"Hello Iris JWT"

如今咱們要對showHello接口加上jwt驗證,須要用到上一小節的jwt中間件工具

package main

import (
  "github.com/kataras/iris/v12"

  "github.com/iris-contrib/middleware/jwt"
)

func main() {
  app := iris.New()

  j := jwt.New(jwt.Config{
    // Extractor屬性能夠選擇從什麼地方獲取jwt進行驗證,默認從http請求的header中的Authorization字段提取,也可指定爲請求參數中的某個字段

    // 從請求參數token中提取
    // Extractor: jwt.FromParameter("token"),

    // 從請求頭的Authorization字段中提取,這個是默認值
    Extractor: jwt.FromAuthHeader,

    // 設置一個函數返回祕鑰,關鍵在於return []byte("這裏設置祕鑰")
    ValidationKeyGetter: func(token *jwt.Token) (interface{}, error) {
      return []byte("My Secret"), nil
    },

    // 設置一個加密方法
    SigningMethod: jwt.SigningMethodHS256,
  })

  app.Get("/getJWT", func(ctx iris.Context) {
    // 往jwt中寫入了一對值
    token := jwt.NewTokenWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
      "foo": "bar",
    })

    // 使用設置的祕鑰,簽名生成jwt字符串
    tokenString, _ := token.SignedString([]byte("My Secret"))

    // 返回
    ctx.JSON(tokenString)
  })

  // 注意這裏加了j.Serve做爲路由的中間件
  app.Get("/showHello", j.Serve, func(ctx iris.Context) {
    ctx.JSON("Hello Iris JWT")
  })

  app.Run(iris.Addr(":8080"))
}

訪問http://localhost:8080/showHello返回

required authorization token not found

因爲開啓了jwt認證,你可能會想到,給Header中加入一個字段Authorization,而後值爲剛剛getJWT接口返回的字符串就能夠成功經過這個驗證了。

測試JWT驗證

Postman(或者其餘測試工具,都同樣)中請求的Headers中加入一對值

kEY VALUE
Authorization eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIifQ.4aQan-XvJBjDUDbCVnuh2P_xy54b2aRKKsgKcHUa8uw

訪問http://localhost:8080/showHello返回

Authorization header format must be Bearer {token}

可能你會納悶,這個Authorization header format must be Bearer {token}是什麼意思?按邏輯來說,咱們的作法應該是沒有錯的。確實,就兩次請求的變化來看,Headers加入的Authorization的的確確被識別到了,可是彷佛還有些小問題,根據提示,是格式上還有些出入。

Postman(或者其餘測試工具,都同樣)中請求的Headers中更改Authorization值爲

kEY VALUE
Authorization JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIifQ.4aQan-XvJBjDUDbCVnuh2P_xy54b2aRKKsgKcHUa8uw

這樣彷佛符合Bearer {token}格式了,訪問http://localhost:8080/showHello返回

Authorization header format must be Bearer {token}

說明格式仍是不對。

繼續修改Headers

kEY VALUE
Authorization Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIifQ.4aQan-XvJBjDUDbCVnuh2P_xy54b2aRKKsgKcHUa8uw

訪問http://localhost:8080/showHello返回

"Hello Iris JWT"

此次終於返回"Hello Iris JWT"了,說實話這個格式要求和這個提示有點坑。

JWT中間件作了什麼

能夠看到,咱們的/showHello接口純粹就是返回"Hello Iris JWT",沒有作任何和JWT有關的操做,可是因爲咱們在路由上使用了JWT中間件,咱們的/showHello接口具備了自動JWT驗證的功能,這是JWT中間件在執行咱們的接口代碼以前,自動幫咱們作的。

修改Headers中的Authorization的值,偷偷把最後一個字符改爲其餘,看看接口是否可以驗證出阿里這是一個假的jwt

kEY VALUE
Authorization(真) Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIifQ.4aQan-XvJBjDUDbCVnuh2P_xy54b2aRKKsgKcHUa8uw
Authorization(假) Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIifQ.4aQan-XvJBjDUDbCVnuh2P_xy54b2aRKKsgKcHUa8uu

訪問http://localhost:8080/showHello返回

signature is invalid

可見不合法的jwt是沒法經過驗證的。

獲取JWT中的值

JWT中間件在驗證失敗的時候,接口中的代碼不會被執行,請求在JWT驗證的時候就被響應signature is invalid並結束。
而當JWT中間件驗證成功的時候,咱們則能夠在接口中獲取到JWT的信息,咱們添加一個接口/showJWT,來輸出JWT的結構:

app.Get("/showJWT", j.Serve, func(ctx iris.Context) {
  jwtInfo := ctx.Values().Get("jwt").(*jwt.Token)
  ctx.JSON(jwtInfo)
})

一樣須要設置Headers帶上jwt

kEY VALUE
Authorization Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIifQ.4aQan-XvJBjDUDbCVnuh2P_xy54b2aRKKsgKcHUa8uw

訪問http://localhost:8080/showJWT返回

{
    "Raw": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIifQ.4aQan-XvJBjDUDbCVnuh2P_xy54b2aRKKsgKcHUa8uw",
    "Method": {
        "Name": "HS256",
        "Hash": 5
    },
    "Header": {
        "alg": "HS256",
        "typ": "JWT"
    },
    "Claims": {
        "foo": "bar"
    },
    "Signature": "4aQan-XvJBjDUDbCVnuh2P_xy54b2aRKKsgKcHUa8uw",
    "Valid": true
}

能夠看到Claims裏面就有咱們簽發jwt的時候寫入的數據了,若是想要在接口中拿到:

app.Get("/showJWTFoo", j.Serve, func(ctx iris.Context) {
  jwtInfo := ctx.Values().Get("jwt").(*jwt.Token)
  foo := jwtInfo.Claims.(jwt.MapClaims)["foo"].(string)
  ctx.JSON(foo)
})

設置Headers帶上jwt

kEY VALUE
Authorization Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIifQ.4aQan-XvJBjDUDbCVnuh2P_xy54b2aRKKsgKcHUa8uw

訪問http://localhost:8080/showJWTFoo返回

"bar"

設置過時時間

因爲jwt簽發出去以後,後端並不存儲,因此至關於後端並不能管理簽發出去的jwt,這種狀況下後端天然不能讓簽發出去的jwt永久有效,須要根據需求設置一個過時時間。

新增一個獲取JWT的接口,此次在Claims中寫入更多的數據:

app.Get("/getJWTWithExp", func(ctx iris.Context) {
  token := jwt.NewTokenWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
    // 根據需求,能夠存一些必要的數據
    "userName": "JabinGP",
    "userId":   "1",
    "admin":    true,

    // 簽發人
    "iss": "iris",
    // 簽發時間
    "iat": time.Now().Unix(),
    // 設定過時時間,便於測試,設置1分鐘過時
    "exp": time.Now().Add(1 * time.Minute * time.Duration(1)).Unix(),
  })

  // 使用設置的祕鑰,簽名生成jwt字符串
  tokenString, _ := token.SignedString([]byte("My Secret"))

  // 返回
  ctx.JSON(tokenString)
})

請求http://localhost:8080/getJWTWithExp返回

"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhZG1pbiI6dHJ1ZSwiZXhwIjoxNTc1Mzc5OTk0LCJpYXQiOjE1NzUzNzk5MzQsImlzcyI6ImlyaXMiLCJ1c2VySWQiOiIxIiwidXNlck5hbWUiOiJKYWJpbkdQIn0.AMFZNbtERLHzKAR9z7oF_0QeNoARKr4dkjocNbVsWgg"

設置Headers帶上新的jwt

kEY VALUE
Authorization Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhZG1pbiI6dHJ1ZSwiZXhwIjoxNTc1Mzc5OTk0LCJpYXQiOjE1NzUzNzk5MzQsImlzcyI6ImlyaXMiLCJ1c2VySWQiOiIxIiwidXNlck5hbWUiOiJKYWJpbkdQIn0.AMFZNbtERLHzKAR9z7oF_0QeNoARKr4dkjocNbVsWgg

訪問localhost:8080/showJWT返回

{
    "Raw": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhZG1pbiI6dHJ1ZSwiZXhwIjoxNTc1Mzc5OTk0LCJpYXQiOjE1NzUzNzk5MzQsImlzcyI6ImlyaXMiLCJ1c2VySWQiOiIxIiwidXNlck5hbWUiOiJKYWJpbkdQIn0.AMFZNbtERLHzKAR9z7oF_0QeNoARKr4dkjocNbVsWgg",
    "Method": {
        "Name": "HS256",
        "Hash": 5
    },
    "Header": {
        "alg": "HS256",
        "typ": "JWT"
    },
    "Claims": {
        "admin": true,
        "exp": 1575379994,
        "iat": 1575379934,
        "iss": "iris",
        "userId": "1",
        "userName": "JabinGP"
    },
    "Signature": "AMFZNbtERLHzKAR9z7oF_0QeNoARKr4dkjocNbVsWgg",
    "Valid": true
}

隔一分鐘後,再次請求localhost:8080/showJWT返回

Token is expired

說明過時時間設置生效。

設置錯誤返回格式

因爲JWT中間件在檢驗jwt發現不合法時,會自動響應返回錯誤信息並結束請求,如上所示,錯誤都是一句話,這樣的錯誤對於前端來講很是不友好,前端更但願能經過一個狀態碼來判斷請求是否順利。

首先定義一個響應數據的模板:

// ResModel 返回數據模板
type ResModel struct {
  Code string      `json:"code"`
  Msg  string      `json:"msg"`
  Data interface{} `json:"data"`
}

獲取一個新的中間件,與以前不一樣的是自定義了錯誤處理函數(扒了源碼改的):

注意引入的context是iris包下的,不然沒法符合錯誤處理函數的定義。

import "github.com/kataras/iris/v12/context"
// j2 對比 j 添加了錯誤處理函數
j2 := jwt.New(jwt.Config{
  // 注意,新增了一個錯誤處理函數
  ErrorHandler: func(ctx context.Context, err error) {
    if err == nil {
      return
    }

    ctx.StopExecution()
    ctx.StatusCode(iris.StatusUnauthorized)
    ctx.JSON(ResModel{
      Code: "501",
      Msg:  err.Error(),
    })
  },
  // 設置一個函數返回祕鑰,關鍵在於return []byte("這裏設置祕鑰")
  ValidationKeyGetter: func(token *jwt.Token) (interface{}, error) {
    return []byte("My Secret"), nil
  },

  // 設置一個加密方法
  SigningMethod: jwt.SigningMethodHS256,
})

在一個新的接口上應用新的j2中間件

app.Get("/showJWTErrWithFormat", j2.Serve, func(ctx iris.Context) {
  jwtInfo := ctx.Values().Get("jwt").(*jwt.Token)
  ctx.JSON(jwtInfo)
})

設置Headers帶上一個過時的jwt

kEY VALUE
Authorization Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhZG1pbiI6dHJ1ZSwiZXhwIjoxNTc1Mzc5OTk0LCJpYXQiOjE1NzUzNzk5MzQsImlzcyI6ImlyaXMiLCJ1c2VySWQiOiIxIiwidXNlck5hbWUiOiJKYWJpbkdQIn0.AMFZNbtERLHzKAR9z7oF_0QeNoARKr4dkjocNbVsWgg

訪問localhost:8080/showJWT返回

{
    "code": "501",
    "msg": "Token is expired",
    "data": null
}

篡改HeadersAuthorization的值,把最後一個字符改爲其餘的:g->o

kEY VALUE
Authorization Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhZG1pbiI6dHJ1ZSwiZXhwIjoxNTc1Mzc5OTk0LCJpYXQiOjE1NzUzNzk5MzQsImlzcyI6ImlyaXMiLCJ1c2VySWQiOiIxIiwidXNlck5hbWUiOiJKYWJpbkdQIn0.AMFZNbtERLHzKAR9z7oF_0QeNoARKr4dkjocNbVsWgo

訪問localhost:8080/showJWT返回

{
    "code": "501",
    "msg": "signature is invalid",
    "data": null
}

至此,jwt基本符合使用需求了,最後附上完整的代碼。

完整代碼

若是文章對你有幫助,點個贊或者留下評論將會是對個人極大鼓勵!
package main

import (
  "time"

  "github.com/kataras/iris/v12"

  "github.com/iris-contrib/middleware/jwt"
  "github.com/kataras/iris/v12/context"
)

// ResModel 返回數據模板
type ResModel struct {
  Code string      `json:"code"`
  Msg  string      `json:"msg"`
  Data interface{} `json:"data"`
}

func main() {
  app := iris.New()

  j := jwt.New(jwt.Config{
    // Extractor屬性能夠選擇從什麼地方獲取jwt進行驗證,默認從http請求的header中的Authorization字段提取,也可指定爲請求參數中的某個字段

    // 從請求參數token中提取
    // Extractor: jwt.FromParameter("token"),

    // 從請求頭的Authorization字段中提取,這個是默認值
    Extractor: jwt.FromAuthHeader,

    // 設置一個函數返回祕鑰,關鍵在於return []byte("這裏設置祕鑰")
    ValidationKeyGetter: func(token *jwt.Token) (interface{}, error) {
      return []byte("My Secret"), nil
    },

    // 設置一個加密方法
    SigningMethod: jwt.SigningMethodHS256,
  })

  // j2 對比 j 添加了錯誤處理函數
  j2 := jwt.New(jwt.Config{
    // 注意,新增了一個錯誤處理函數
    ErrorHandler: func(ctx context.Context, err error) {
      if err == nil {
        return
      }

      ctx.StopExecution()
      ctx.StatusCode(iris.StatusUnauthorized)
      ctx.JSON(ResModel{
        Code: "501",
        Msg:  err.Error(),
      })
    },
    // 設置一個函數返回祕鑰,關鍵在於return []byte("這裏設置祕鑰")
    ValidationKeyGetter: func(token *jwt.Token) (interface{}, error) {
      return []byte("My Secret"), nil
    },

    // 設置一個加密方法
    SigningMethod: jwt.SigningMethodHS256,
  })

  app.Get("/getJWT", func(ctx iris.Context) {
    // 往jwt中寫入了一對值
    token := jwt.NewTokenWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
      "foo": "bar",
    })

    // 使用設置的祕鑰,簽名生成jwt字符串
    tokenString, _ := token.SignedString([]byte("My Secret"))

    // 返回
    ctx.JSON(tokenString)
  })
  app.Get("/getJWTWithExp", func(ctx iris.Context) {
    token := jwt.NewTokenWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
      // 根據需求,能夠存一些必要的數據
      "userName": "JabinGP",
      "userId":   "1",
      "admin":    true,

      // 簽發人
      "iss": "iris",
      // 簽發時間
      "iat": time.Now().Unix(),
      // 設定過時時間,便於測試,設置1分鐘過時
      "exp": time.Now().Add(1 * time.Minute * time.Duration(1)).Unix(),
    })

    // 使用設置的祕鑰,簽名生成jwt字符串
    tokenString, _ := token.SignedString([]byte("My Secret"))

    // 返回
    ctx.JSON(tokenString)
  })

  app.Get("/showHello", j.Serve, func(ctx iris.Context) {
    ctx.JSON("Hello Iris JWT")
  })

  app.Get("/showJWT", j.Serve, func(ctx iris.Context) {
    jwtInfo := ctx.Values().Get("jwt").(*jwt.Token)
    ctx.JSON(jwtInfo)
  })

  app.Get("/showJWTFoo", j.Serve, func(ctx iris.Context) {
    jwtInfo := ctx.Values().Get("jwt").(*jwt.Token)
    foo := jwtInfo.Claims.(jwt.MapClaims)["foo"].(string)
    ctx.JSON(foo)
  })

  app.Get("/showJWTErrWithFormat", j2.Serve, func(ctx iris.Context) {
    jwtInfo := ctx.Values().Get("jwt").(*jwt.Token)
    ctx.JSON(jwtInfo)
  })

  app.Run(iris.Addr(":8080"))
}
相關文章
相關標籤/搜索