因爲jwt原理已經有不少文章說起過了,這裏再也不贅述,本文主要介紹jwt在iris中的實踐,文章的最後會給出完整代碼,能夠運行起來邊測試邊看。
若是文章對你有幫助,點個贊或者留下評論將會是對個人極大鼓勵!
本文將jwt用於登陸功能,若是是其餘功能需求其實也相似,能夠舉一反三。前端
iris基於中間件思想設計,對於一些重複性的操做,能夠經過註冊中間件來完成。對於登陸功能,咱們天然不但願每一個api中手動判斷是否過時,是否合法等,由於咱們須要一個jwt中間件來完成這些操做。git
這是一個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
因爲並不是全部接口都須要設置登陸攔截,因此將中間件用於路由,或者路由組是比較符合業務需求的作法後端
先給出兩個接口:api
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
接口返回的字符串就能夠成功經過這個驗證了。
在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"
了,說實話這個格式要求和這個提示有點坑。
能夠看到,咱們的/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驗證的時候就被響應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 |
{ "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 |
{ "code": "501", "msg": "Token is expired", "data": null }
篡改Headers
中Authorization
的值,把最後一個字符改爲其餘的:g
->o
kEY | VALUE |
---|---|
Authorization | Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhZG1pbiI6dHJ1ZSwiZXhwIjoxNTc1Mzc5OTk0LCJpYXQiOjE1NzUzNzk5MzQsImlzcyI6ImlyaXMiLCJ1c2VySWQiOiIxIiwidXNlck5hbWUiOiJKYWJpbkdQIn0.AMFZNbtERLHzKAR9z7oF_0QeNoARKr4dkjocNbVsWgo |
{ "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")) }