Repository: appleboy/gin-jwt
Pull request #305 (2022-09-29 01:59:31)

bug: fix the false alarm of verification with jwt.WithJSONNumber
The current implementation doesn't handle `jwt.WithJSONNumber()` parse option well, if you set this option and try to parse a JWT, it always fails type conversion here:

https://github.com/appleboy/gin-jwt/blob/6b68cb3e1879fc8f8936ee7a7cbb65e40e389e66/auth_jwt.go#L430-L433

We should also take care of the type `json.Number` and avoid the false alarm, this is also what https://github.com/golang-jwt/jwt/blob/main/map_claims.go#L40-L56 does.

panjf2000: cc @appleboy 
panjf2000: any comments on this?
@appleboy 
appleboy: LGTM @panjf2000 Thanks.

Patch:
diff --git a/_example/basic/server.go b/_example/basic/server.go
index 7e74654..f3d71c6 100644
--- a/_example/basic/server.go
+++ b/_example/basic/server.go
@@ -6,8 +6,9 @@ import (
 	"os"
 	"time"
 
-	jwt "github.com/appleboy/gin-jwt/v2"
 	"github.com/gin-gonic/gin"
+
+	jwt "github.com/appleboy/gin-jwt/v2"
 )
 
 type login struct {
@@ -112,7 +113,6 @@ func main() {
 		// TimeFunc provides the current time. You can override it to use another time value. This is useful for testing or if your server uses a different time zone than your tokens.
 		TimeFunc: time.Now,
 	})
-
 	if err != nil {
 		log.Fatal("JWT Error:" + err.Error())
 	}
diff --git a/auth_jwt.go b/auth_jwt.go
index 804a334..3db5aca 100644
--- a/auth_jwt.go
+++ b/auth_jwt.go
@@ -2,6 +2,7 @@ package jwt
 
 import (
 	"crypto/rsa"
+	"encoding/json"
 	"errors"
 	"net/http"
 	"os"
@@ -422,21 +423,30 @@ func (mw *GinJWTMiddleware) middlewareImpl(c *gin.Context) {
 		return
 	}
 
-	if claims["exp"] == nil {
+	switch v := claims["exp"].(type) {
+	case nil:
 		mw.unauthorized(c, http.StatusBadRequest, mw.HTTPStatusMessageFunc(ErrMissingExpField, c))
 		return
-	}
-
-	if _, ok := claims["exp"].(float64); !ok {
+	case float64:
+		if int64(v) < mw.TimeFunc().Unix() {
+			mw.unauthorized(c, http.StatusUnauthorized, mw.HTTPStatusMessageFunc(ErrExpiredToken, c))
+			return
+		}
+	case json.Number:
+		n, err := v.Int64()
+		if err != nil {
+			mw.unauthorized(c, http.StatusBadRequest, mw.HTTPStatusMessageFunc(ErrWrongFormatOfExp, c))
+			return
+		}
+		if n < mw.TimeFunc().Unix() {
+			mw.unauthorized(c, http.StatusUnauthorized, mw.HTTPStatusMessageFunc(ErrExpiredToken, c))
+			return
+		}
+	default:
 		mw.unauthorized(c, http.StatusBadRequest, mw.HTTPStatusMessageFunc(ErrWrongFormatOfExp, c))
 		return
 	}
 
-	if int64(claims["exp"].(float64)) < mw.TimeFunc().Unix() {
-		mw.unauthorized(c, http.StatusUnauthorized, mw.HTTPStatusMessageFunc(ErrExpiredToken, c))
-		return
-	}
-
 	c.Set("JWT_PAYLOAD", claims)
 	identity := mw.IdentityHandler(c)
 


Test patch:
diff --git a/auth_jwt_test.go b/auth_jwt_test.go
index a753036..b239a3a 100644
--- a/auth_jwt_test.go
+++ b/auth_jwt_test.go
@@ -665,6 +665,32 @@ func TestAuthorizator(t *testing.T) {
 		})
 }
 
+func TestParseTokenWithJsonNumber(t *testing.T) {
+	authMiddleware, _ := New(&GinJWTMiddleware{
+		Realm:         "test zone",
+		Key:           key,
+		Timeout:       time.Hour,
+		MaxRefresh:    time.Hour * 24,
+		Authenticator: defaultAuthenticator,
+		Unauthorized: func(c *gin.Context, code int, message string) {
+			c.String(code, message)
+		},
+		ParseOptions: []jwt.ParserOption{jwt.WithJSONNumber()},
+	})
+
+	handler := ginHandler(authMiddleware)
+
+	r := gofight.New()
+
+	r.GET("/auth/hello").
+		SetHeader(gofight.H{
+			"Authorization": "Bearer " + makeTokenString("HS256", "admin"),
+		}).
+		Run(handler, func(r gofight.HTTPResponse, rq gofight.HTTPRequest) {
+			assert.Equal(t, http.StatusOK, r.Code)
+		})
+}
+
 func TestClaimsDuringAuthorization(t *testing.T) {
 	// the middleware to test
 	authMiddleware, _ := New(&GinJWTMiddleware{