返回

简单讲讲我在Gin中使用JWT的续签思路

后端

在Gin中使用JWT进行认证

JWT(JSON Web Token)是一种流行的认证机制,可以用来验证用户身份并授权访问受保护的资源。在Gin中使用JWT进行认证非常简单,我们可以使用一个现成的库,比如gin-gonic/gin-jwt。

import (
	"github.com/gin-gonic/gin"
	"github.com/gin-gonic/gin-jwt"
)

func main() {
	r := gin.Default()

	// 设置JWT中间件
	authMiddleware, err := ginjwt.New(&ginjwt.GinJWTMiddleware{
		Realm:      "my realm",
		Key:        []byte("my secret key"),
		Timeout:    time.Hour,
		MaxRefresh: time.Hour * 24,
	})
	if err != nil {
		panic(err)
	}

	// 认证路由组
	auth := r.Group("/auth")
	auth.POST("/login", authMiddleware.LoginHandler)

	// 私有路由组
	private := r.Group("/private")
	private.Use(authMiddleware.MiddlewareFunc())
	private.GET("/data", func(c *gin.Context) {
		c.JSON(200, gin.H{
			"data": "hello world",
		})
	})

	r.Run()
}

这个代码创建了一个Gin路由器,并使用gin-gonic/gin-jwt库设置了JWT中间件。我们可以在/auth/login路由上进行登录,成功登录后会返回一个JWT令牌。这个令牌可以用来访问/private路由组下的受保护资源。

JWT的续签方案

JWT令牌通常都有一个有限的有效期,为了保持用户登录状态,我们需要在令牌过期之前对其进行续签。通常的做法是让前端在令牌即将过期时向服务端发送请求,服务端收到请求后会生成一个新的令牌返回给前端。

但是,这种方法存在一个问题,那就是服务端需要保存令牌的状态,这可能会导致性能问题。为了解决这个问题,我设计了一个简单的续签方案,不需要服务端保存令牌的状态。

这个方案的关键在于,我们会在JWT令牌中存储一个刷新令牌。刷新令牌是一个比JWT令牌更长的令牌,它可以用来生成新的JWT令牌。当JWT令牌即将过期时,前端可以向服务端发送请求,服务端收到请求后会验证刷新令牌,如果刷新令牌有效,则会生成一个新的JWT令牌返回给前端。

这样,我们就避免了服务端需要保存令牌的状态,也避免了前端需要过多的配合。

这个续签方案的具体实现可以参考以下代码:

import (
	"fmt"
	"time"

	"github.com/golang-jwt/jwt"
)

func main() {
	// 生成JWT令牌
	jwtToken, err := generateJWTToken("username", "user@example.com")
	if err != nil {
		panic(err)
	}

	// 生成刷新令牌
	refreshToken, err := generateRefreshToken("username", "user@example.com")
	if err != nil {
		panic(err)
	}

	// 在JWT令牌中存储刷新令牌
	jwtToken.Claims.(jwt.MapClaims)["refreshToken"] = refreshToken

	fmt.Println("JWT令牌:", jwtToken)
	fmt.Println("刷新令牌:", refreshToken)

	// 续签JWT令牌
	newJWTToken, err := renewJWTToken(jwtToken, refreshToken)
	if err != nil {
		panic(err)
	}

	fmt.Println("新的JWT令牌:", newJWTToken)
}

func generateJWTToken(username, email string) (*jwt.Token, error) {
	claims := jwt.MapClaims{
		"username": username,
		"email":    email,
		"exp":      time.Now().Add(time.Hour).Unix(),
	}

	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
	return token.SignedString([]byte("my secret key"))
}

func generateRefreshToken(username, email string) (string, error) {
	return uuid.NewV4().String(), nil
}

func renewJWTToken(jwtToken *jwt.Token, refreshToken string) (*jwt.Token, error) {
	claims := jwtToken.Claims.(jwt.MapClaims)

	// 验证刷新令牌
	if claims["refreshToken"] != refreshToken {
		return nil, errors.New("invalid refresh token")
	}

	// 生成新的JWT令牌
	newClaims := jwt.MapClaims{
		"username": claims["username"],
		"email":    claims["email"],
		"exp":      time.Now().Add(time.Hour).Unix(),
	}

	newToken := jwt.NewWithClaims(jwt.SigningMethodHS256, newClaims)
	return newToken.SignedString([]byte("my secret key"))
}

这个代码演示了如何生成JWT令牌、刷新令牌,以及如何使用刷新令牌续签JWT令牌。

总结

在本文中,我讨论了如何在Gin中使用JWT进行认证,以及我为实现JWT续签而想出的一个简单的方案。这个方案不需要服务端保存Token的状态,前端也不需要过多的配合。