因为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")) }