jwt认证框架gin-jwt中间件使用说明 | 青训营笔记

691 阅读6分钟

这是我参与「第五届青训营」伴学笔记创作活动的第14天。

之前我们介绍了一种简单使用的jwt-go框架,# GO语言的jwt-go认证框架 | 青训营笔记。现在我们来介绍一下gin里带的jwt框架。这个框架比之前的jwt-go加复杂。但是功能、性能更加强大。

1. 安装 gin-jwt 库

go get -u github.com/appleboy/gin-jwt

2. 创建一个 JWT 的 middleware

package main

import (
	"time"

	"github.com/appleboy/gin-jwt"
	"github.com/gin-gonic/gin"
)
//定义一个登录结构体
type login struct {
	Username string `form:"username" json:"username" binding:"required"`
	Password string `form:"password" json:"password" binding:"required"`
}

// User demo
var User = map[string]string{
	"user1": "password1",
	"user2": "password2",
}

func helloHandler(c *gin.Context) {
	claims := jwt.ExtractClaims(c)
	user, _ := c.Get(identityKey)
	c.JSON(200, gin.H{
		"userID": claims["id"],
		"userName": user.(*User).UserName,
		"text": "Hello World.",
	})
}

func main() {
	
	r := gin.Default()
	authMiddleware := &jwt.GinJWTMiddleware{ // the jwt middleware,jwt中间件的配置参数见文章末尾。
		Realm: "test zone",
		Key: []byte("secret key"),//加密秘钥,自己记得即可,随意取
		Timeout: time.Hour,//有效时间
		MaxRefresh: time.Hour,//最大刷新时间
		IdentityKey: identityKey,
		IdentityHandler: func(c *gin.Context) interface{} {
			claims := jwt.ExtractClaims(c)
			return &User{
				UserName: claims["id"].(string),
			}
		},
		PayloadFunc: func(data interface{}) jwt.MapClaims {
			if v, ok := data.(*User); ok {
				return jwt.MapClaims{
					"id": v.UserName,
				}
			}
			return jwt.MapClaims{}
		},
		Authenticator: func(c *gin.Context) (interface{}, error) { //验证登录的中间件函数
			var loginVals login
			if err := c.ShouldBind(&loginVals); err != nil {
				return "", jwt.ErrMissingLoginValues
			}
			userID := loginVals.Username
			password := loginVals.Password

			if (User[userID] == password) {
				return &User{
					UserName: userID,
				}, nil
			}

			return nil, jwt.ErrFailedAuthentication
		},
		Authorizator: func(data interface{}, c *gin.Context) bool { //验证权限的中间件函数
			if v, ok := data.(*User); ok && v.UserName == "admin" {
				return true
			}

			return false
		},
		Unauthorized: func(c *gin.Context, code int, message string) {
			c.JSON(code, gin.H{
				"code": code,
				"message": message,
			})
		},
		TokenLookup: "header: Authorization, query: token, cookie: jwt",
		TokenHeadName: "Bearer",
		TimeFunc: time.Now,
	}

	r.POST("/login", authMiddleware.LoginHandler) //单个路由中间件使用方法
	r.GET("/refresh_token", authMiddleware.RefreshHandler)

	auth := r.Group("/auth") //设置路由组
	auth.Use(authMiddleware.MiddlewareFunc()) //中间件使用方法:路由r.use(authMiddleware.funcnamexxx())
	{
		auth.GET("/hello", helloHandler)
	}

	if err := r.Run(":8080"); err != nil {
		log.Fatal(err)
	}
}

3. 发送一个登录请求(也可以使用postman测试,下面所有请求都可以,只要请求参数一致即可。)

curl -X POST http://localhost:8080/login -d '{"username":"user1","password":"password1"}'

4. 返回登录结果

{ "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1Mzg5MzkxMjMsImlkIjoidXNlcjEifQ.4m4YjZBzmZQQK_X9bVrKP8W0QVUvk-jK7a-0p8A8O6Y" }

5. 发送一个 hello 请求 (取出token,放入请求头,浏览器或postman会自动放)

curl -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1Mzg5MzkxMjMsImlkIjoidXNlcjEifQ.4m4YjZBzmZQQK_X9bVrKP8W0QVUvk-jK7a-0p8A8O6Y" http://localhost:8080/auth/hello

6. 返回请求结果

{ "text": "Hello World.", "userID": "user1", "userName": "user1" }

7. 发送一个刷新 token 的请求

curl -X GET http://localhost:8080/refresh_token -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1Mzg5MzkxMjMsImlkIjoidXNlcjEifQ.4m4YjZBzmZQQK_X9bVrKP8W0QVUvk-jK7a-0p8A8O6Y"

8. 返回刷新结果

{ "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1Mzg5MzkxNzQsImlkIjoidXNlcjEifQ.W0zB1FxRXg_lPv7zWG8Hk-d2q3-k-v7z1Z8bQx2Qq3o" }

9.错误提示

  • 如果我们不提供正确的 token 值,会返回 401 的错误信息,如下: { "code": 401, "message": "Token not found" }
  • 如果我们提供了正确的 token 值,但是这个 token 已经过期,会返回 401 的错误信息,如下:{ "code": 401, "message": "Token is expired" }
  • 如果我们提供了正确的 token 值,但是这个 token 的使用者没有访问 /auth/hello 的权限,会返回 401 的错误信息,如下: { "code": 401, "message": "Authorization header not found" }

10.中间件里提供的函数的使用说明

1. 登录请求流程(使用 LoginHandler)

提供:LoginHandler() 这是一个提供的函数,可以调用任何登录端点,将触发下面描述的流程。

必需:Authenticator 该函数应验证给定gin上下文中的用户凭据(例如,给定用户电子邮件的密码与哈希密码匹配,以及任何其他身份验证逻辑)。然后,Authenticator应返回一个包含将嵌入jwt令牌中的用户数据的结构或映射。这可能是账号ID,角色,is_verified等之类的东西。在成功认证后,从Authenticator返回的数据作为参数传递到PayloadFunc中,用于将上述用户标识嵌入jwt令牌中。如果返回错误,将使用Unauthorized函数(如下所述)。

可选:PayloadFunc 在成功认证(登陆)后调用此函数。它应将从Authenticator返回的任何内容转换为MapClaims(即map [string] interface {})。这个函数的典型用例是,当Authenticator返回一个持有用户标识符的结构时,该结构需要转换为一个映射。MapClaims应包括一个元素[IdentityKey(默认为“identity”):some_user_identity]。 PayloadFunc返回的MapClaims中的元素将嵌入jwt令牌(作为令牌声明)中。当用户在后续请求中传递令牌时,可以通过使用ExtractClaims返回这些要求。

可选:LoginResponse 在成功使用Authenticator认证,使用从PayloadFunc返回的映射中的标识符创建jwt令牌,并在SendCookie启用时将其设置为cookie之后,调用此函数。它被用于处理任何登录后的逻辑。这可能看起来像是使用gin上下文将令牌的JSON返回给用户。

2.登陆后,该端点上的后续请求需要 jwt 令牌(使用 MiddlewareFunc)

提供:MiddlewareFunc()

这是 gin 中间件,应该在任何需要 jwt 令牌的端点中使用。 如果令牌存在,此中间件将解析请求标头,并检查 jwt 令牌是否有效(未过期,签名正确)。 然后它将调用 IdentityHandler,然后调用 Authorizator。 如果 Authorizator 通过并且所有先前的令牌有效性检查都通过,则中间件将继续请求。 如果这些检查中的任何一个失败,则使用 Unauthorized 函数(如下所述)。

可选:IdentityHandler

此功能的默认值可能足以满足您的需要。 此函数的目的是从嵌入在 jwt 令牌中的声明中获取用户身份,并将此身份值传递给 Authorizator。 此函数假定 [IdentityKey: some_user_identity] 是嵌入在 jwt 令牌声明中的属性之一(由 PayloadFunc 确定)。

可选:Authorizator

给定用户身份值(data参数)和 gin 上下文,此函数应检查用户是否有权到达此端点(在 MiddlewareFunc 适用的端点上)。 此函数可能应该使用 ExtractClaims 来检查用户是否具有足够的权限来访问此端点,而不是在每次请求时都访问数据库。 如果用户被授权继续执行请求,则此函数应返回 true,如果他们未被授权(将调用 Unauthorized),则返回 false。

3.注销请求流程(使用 LogoutHandler)

提供:LogoutHandler()

这是一个提供的函数,可以在任何注销端点上调用,如果设置了 SendCookie,它将清除所有 cookie,然后调用 LogoutResponse。

可选:LogoutResponse

如果注销成功与否,这可能只是将 http 状态代码返回给用户。

4. 刷新请求流程(使用 RefreshHandler)

提供:RefreshHandler

这是一个提供的函数,可以在任何刷新令牌端点上调用。 如果传入的令牌是在 MaxRefreshTime 时间范围内发出的,则此处理程序将创建/设置一个类似于 LoginHandler 的新令牌,并将此令牌传递给 RefreshResponse

可选:RefreshResponse

这可能会将令牌的 JSON 返回给用户,类似于 LoginResponse

5. 登录失败、令牌错误或缺少权限

可选: Unauthorized

在登录、授权用户时出现任何错误,或者当请求中没有令牌或无效令牌时,将发生以下情况。 gin 上下文将根据 DisabledAbort 中止,然后调用 HTTPStatusMessageFunc,默认情况下将错误转换为字符串。 最后将调用 Unauthorized 函数。 此函数应该可能会向用户返回包含 http 错误代码和错误消息的 JSON。