From 78b617ad1ebd5ffb87ec132c00e370fac721a44f Mon Sep 17 00:00:00 2001 From: Sunhill666 Date: Thu, 3 Jul 2025 14:19:50 +0800 Subject: [PATCH] Add ParserOption config to support JWT parsing options, with corresponding unit test. --- jwt.go | 8 +++++++- jwt_test.go | 53 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+), 1 deletion(-) diff --git a/jwt.go b/jwt.go index 1e97d30..b16af77 100644 --- a/jwt.go +++ b/jwt.go @@ -102,6 +102,12 @@ type Config struct { // Defaults to implementation using `github.com/golang-jwt/jwt` as JWT implementation library ParseTokenFunc func(c echo.Context, auth string) (interface{}, error) + // ParserOption defines a list of options that are passed to the JWT parser. + // This is used to configure the parser behavior, such as whether to validate the token's expiration time, + // whether to validate the token's audience, etc. + // Optional. Default value is empty slice. + ParserOption []jwt.ParserOption + // Claims are extendable claims data defining token content. Used by default ParseTokenFunc implementation. // Not used if custom ParseTokenFunc is set. // Optional. Defaults to function returning jwt.MapClaims @@ -286,7 +292,7 @@ func (config Config) defaultKeyFunc(token *jwt.Token) (interface{}, error) { // // error returns TokenError. func (config Config) defaultParseTokenFunc(c echo.Context, auth string) (interface{}, error) { - token, err := jwt.ParseWithClaims(auth, config.NewClaimsFunc(c), config.KeyFunc) + token, err := jwt.ParseWithClaims(auth, config.NewClaimsFunc(c), config.KeyFunc, config.ParserOption...) if err != nil { return nil, &TokenError{Token: token, Err: err} } diff --git a/jwt_test.go b/jwt_test.go index ecd26ad..9714c10 100644 --- a/jwt_test.go +++ b/jwt_test.go @@ -11,6 +11,7 @@ import ( "net/url" "strings" "testing" + "time" "github.com/golang-jwt/jwt/v5" "github.com/labstack/echo/v4" @@ -628,6 +629,58 @@ func TestConfig_custom_ParseTokenFunc_Keyfunc(t *testing.T) { assert.Equal(t, http.StatusTeapot, res.Code) } +func TestConfig_parseOptions(t *testing.T) { + e := echo.New() + e.GET("/", func(c echo.Context) error { + return c.String(http.StatusTeapot, "test") + }) + + signingKey := []byte("secret") + expiredClaim := jwt.MapClaims{ + "admin": true, + "name": "John Doe", + "sub": "1234567890", + "exp": time.Now().Add(-10 * time.Second).Unix(), // expired 10 seconds ago + } + testClaim := jwt.MapClaims{ + "admin": true, + "name": "John Doe", + "sub": "1234567890", + "exp": time.Now().Add(-2 * time.Second).Unix(), + } + + expiredToken := jwt.NewWithClaims(jwt.SigningMethodHS256, expiredClaim) + expiredTokenString, err := expiredToken.SignedString(signingKey) + if err != nil { + t.Fatalf("failed to sign expired token: %v", err) + } + + testToken := jwt.NewWithClaims(jwt.SigningMethodHS256, testClaim) + testTokenString, err := testToken.SignedString(signingKey) + if err != nil { + t.Fatalf("failed to sign test token: %v", err) + } + + config := Config{ + SigningKey: signingKey, + ParserOption: []jwt.ParserOption{jwt.WithLeeway(5 * time.Second)}, // allow 5 seconds leeway for token expiration + } + + e.Use(WithConfig(config)) + + req := httptest.NewRequest(http.MethodGet, "/", nil) + req.Header.Set(echo.HeaderAuthorization, "Bearer "+expiredTokenString) + res := httptest.NewRecorder() + e.ServeHTTP(res, req) + assert.Equal(t, http.StatusUnauthorized, res.Code) + + req = httptest.NewRequest(http.MethodGet, "/", nil) + req.Header.Set(echo.HeaderAuthorization, "Bearer "+testTokenString) + res = httptest.NewRecorder() + e.ServeHTTP(res, req) + assert.Equal(t, http.StatusTeapot, res.Code) +} + func TestMustJWTWithConfig_SuccessHandler(t *testing.T) { e := echo.New()