由于jwt原理已经有很多文章提及过了,这里不再赘述,本文主要介绍jwt在iris中的实践,文章的最后会给出完整代码,可以运行起来边测试边看。
如果文章对你有帮助,点个赞或者留下评论将会是对我的极大鼓励!
jwt使用方向
本文将jwt用于登录功能,如果是其他功能需求其实也类似,可以触类旁通。
iris中jwt的使用思想
iris基于中间件思想设计,对于一些重复性的操作,可以通过注册中间件来完成。对于登录功能,我们自然不希望每个api中手动判断是否过期,是否合法等,因为我们需要一个jwt中间件来完成这些操作。
获取jwt中间件
这是一个iris官方提供的jwt中间件:
import "github.com/iris-contrib/middleware/jwt"
用法如下:
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
使用jwt中间件
由于并非所有接口都需要设置登录拦截,所以将中间件用于路由,或者路由组是比较符合业务需求的做法
先给出两个接口:
- “/getJWT”:生成jwt并返回
- “/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返回一串类似这样的字符串
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIifQ.4aQan-XvJBjDUDbCVnuh2P_xy54b2aRKKsgKcHUa8uw"
"Hello Iris JWT"
showHello
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")) }
required authorization token not found
HeaderAuthorizationgetJWT
测试JWT验证
PostmanHeaders
kEY | VALUE |
---|---|
Authorization | eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIifQ.4aQan-XvJBjDUDbCVnuh2P_xy54b2aRKKsgKcHUa8uw |
Authorization header format must be Bearer {token}
Authorization header format must be Bearer {token}HeadersAuthorization
PostmanHeadersAuthorization
kEY | VALUE |
---|---|
Authorization | JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIifQ.4aQan-XvJBjDUDbCVnuh2P_xy54b2aRKKsgKcHUa8uw |
Bearer {token}
Authorization header format must be Bearer {token}
说明格式还是不对。
Headers
kEY | VALUE |
---|---|
Authorization | Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIifQ.4aQan-XvJBjDUDbCVnuh2P_xy54b2aRKKsgKcHUa8uw |
"Hello Iris JWT"
"Hello Iris JWT"
JWT中间件做了什么
/showHello"Hello Iris JWT"/showHello自动JWT验证
HeadersAuthorization
kEY | VALUE |
---|---|
Authorization(真) | Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIifQ.4aQan-XvJBjDUDbCVnuh2P_xy54b2aRKKsgKcHUa8uw |
Authorization(假) | Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIifQ.4aQan-XvJBjDUDbCVnuh2P_xy54b2aRKKsgKcHUa8uu |
signature is invalid
可见不合法的jwt是无法通过验证的。
获取JWT中的值
signature is invalid/showJWT
app.Get("/showJWT", j.Serve, func(ctx iris.Context) { jwtInfo := ctx.Values().Get("jwt").(*jwt.Token) ctx.JSON(jwtInfo) })
Headers
kEY | VALUE |
---|---|
Authorization | Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIifQ.4aQan-XvJBjDUDbCVnuh2P_xy54b2aRKKsgKcHUa8uw |
{ "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
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
kEY | VALUE |
---|---|
Authorization | Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIifQ.4aQan-XvJBjDUDbCVnuh2P_xy54b2aRKKsgKcHUa8uw |
"bar"
设置过期时间
由于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) })
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhZG1pbiI6dHJ1ZSwiZXhwIjoxNTc1Mzc5OTk0LCJpYXQiOjE1NzUzNzk5MzQsImlzcyI6ImlyaXMiLCJ1c2VySWQiOiIxIiwidXNlck5hbWUiOiJKYWJpbkdQIn0.AMFZNbtERLHzKAR9z7oF_0QeNoARKr4dkjocNbVsWgg"
Headers
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
kEY | VALUE |
---|---|
Authorization | Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhZG1pbiI6dHJ1ZSwiZXhwIjoxNTc1Mzc5OTk0LCJpYXQiOjE1NzUzNzk5MzQsImlzcyI6ImlyaXMiLCJ1c2VySWQiOiIxIiwidXNlck5hbWUiOiJKYWJpbkdQIn0.AMFZNbtERLHzKAR9z7oF_0QeNoARKr4dkjocNbVsWgg |
{ "code": "501", "msg": "Token is expired", "data": null }
HeadersAuthorizationgo
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")) }