Golang中如何从JWT的key中获取值

Golang中如何从JWT的key中获取值 在Echo框架网站上,我按照他们的指南创建了一个JWT,并定义了一个自定义声明类型来存储和传递用户的电子邮件到其他请求中。然而,在遵循指南后,我创建了一个函数来将令牌存储到数据库,但在尝试从“email”键中检索值时,它返回了一个错误,声称无法将声明转换为JWT。我测试了创建JWT的函数是否出了问题,但它返回的编码令牌是正常的。那么,我应该如何将JWT传递给我的电子邮件函数以从中提取值呢?

sessionmanager

type Session struct {
	Token  string
	Data   string
	Expiry time.Time
}

type UserCustomClaims struct {
	Email      string `json:"email"`
	jwt.RegisteredClaims
}

func Email(c echo.Context) (string, error) {
	user, ok := c.Get("user").(*jwt.Token)
	if !ok {
		return "", echo.NewHTTPError(http.StatusBadRequest, "error:sessionamanager: failed to convert to jwt token")
	}
	claims, ok := user.Claims.(*UserCustomClaims)
	if !ok {
		return "", echo.NewHTTPError(http.StatusBadRequest, "error:sessionamanager: failed to convert to customs claims type")
	}
	email := claims.Email
	if email == "" {
		return "", echo.NewHTTPError(http.StatusBadRequest, "failed to convert email")
	}
	return email, nil
}

// adds user token to sessions table in database
func CreateToken(usr string, c echo.Context) (*jwt.Token, error) {
	expirDate := time.Now().Add(time.Hour * 72).Unix()
	claims := &UserCustomClaims{
		Email:      usr,
		RegisteredClaims: jwt.RegisteredClaims{
			ExpiresAt: jwt.NewNumericDate(time.Unix(expirDate, 0)),
		},
	}

	fmt.Printf("foo: %v\n", claims.Email)

	// Create token with claims
	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)

	return token, nil
}

// adds user token to sessions table in database
func AddToken(tk *jwt.Token, c echo.Context) error {
	ctx, db := backend.Connect()
	defer db.Close()

	// Generate encoded token and send it as response.
	t, err := tk.SignedString([]byte("secret"))
	if err != nil {
		return err
	}

	email, emailErr := Email(c)
	if emailErr  != nil {
		return fmt.Errorf("error: failed to retreive to email from claim: %w", emailErr)
	}
	expTime, err := tk.Claims.GetExpirationTime()

	// err retrieving expiration time
	if err != nil {
		return errors.New("error: failed to get expiration time")
	}

	// insert token
	_, err = db.Exec(
		ctx,
		`INSERT INTO sessions (token, data, expiry) VALUES($1, $2, $3)`,
		t, email, expTime.Time,
	)

	// handle multiple errs when insertion goes wrong
	if err != nil {
		return fmt.Errorf("error: failed to add usersession: %w", err)
	}
	return nil
}

主文件

import (
	"errors"
	"fmt"
	"html/template"
	"io"
	"log"
	"net/http"

	"scriptmang/drumstick/internal/sessionmanager"

	"github.com/golang-jwt/jwt/v5"
	echojwt "github.com/labstack/echo-jwt/v4"
	"github.com/labstack/echo/v4"
	"github.com/labstack/echo/v4/middleware"
)

type TemplateManager struct {
	templates *template.Template
}

func (tm *TemplateManager) Render(w io.Writer, name string, data interface{}, c echo.Context) error {
	if viewContext, isMap := data.(map[string]interface{}); isMap {
		viewContext["reverse"] = c.Echo().Reverse
	}
	err := tm.templates.ExecuteTemplate(w, name, data)

	if err != nil {
		log.Println("template not found")
	}

	return err
}

func loginForm(c echo.Context) error {
	str := "Login to Drumstick"
	return c.Render(http.StatusOK, "loginForm", str)
}

func vetLogin(c echo.Context) error {
	usr := c.FormValue("email")
	pswd := c.FormValue("password")

	rsltErr := accts.CompareUserCreds(usr, pswd)
	if rsltErr != nil {
		return echo.NewHTTPError(http.StatusUnauthorized, rsltErr)
	}

	tk, err := sessionmanager.CreateToken(usr, c)
	if err != nil {
		return echo.NewHTTPError(http.StatusBadRequest, err)
	}

    // store the token to the database
	err = sessionmanager.AddToken(tk, c)
	if err != nil {
		return echo.NewHTTPError(http.StatusBadRequest, err)
	}

	return c.Render(http.StatusOK, "posts", "Add Posts to Your Feed")
}

func main() {
	tm := &TemplateManager{
		templates: template.Must(template.ParseGlob("ui/html/pages/*[^#?!|].tmpl")),
	}
    router.Renderer = tm
	router := echo.New()

	router.Use(middleware.SecureWithConfig(middleware.DefaultSecureConfig))
	router.Use(middleware.Logger())
	router.Use(middleware.Recover())

    router.GET("/loginForm", loginForm)
	router.POST("/posts", vetLogin)

	// Configure middleware with the custom claims type
	config := echojwt.Config{
		NewClaimsFunc: func(c echo.Context) jwt.Claims {
			return new(sessionmanager.UserCustomClaims)
		},
		SigningKey: []byte("secret"),
	}
	router.Use(echojwt.WithConfig(config))

	router.Logger.Fatal(router.Start(":8080"))
}

JWT | Echo

Middleware | Echo

echo package - github.com/labstack/echo/v4 - Go Packages

jwt package - github.com/golang-jwt/jwt/v5 - Go Packages


更多关于Golang中如何从JWT的key中获取值的实战教程也可以访问 https://www.itying.com/category-94-b0.html

1 回复

更多关于Golang中如何从JWT的key中获取值的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


在Echo框架中从JWT获取自定义声明值时,问题通常出现在中间件的配置和上下文传递上。根据你的代码,主要问题是在AddToken函数中调用Email(c)时,JWT中间件尚未处理当前请求,因此c.Get("user")返回的是nil

以下是修复后的代码示例:

sessionmanager.go - 修改Email函数使其直接处理token:

func GetEmailFromToken(tk *jwt.Token) (string, error) {
    claims, ok := tk.Claims.(*UserCustomClaims)
    if !ok {
        return "", fmt.Errorf("failed to convert to custom claims type")
    }
    
    if claims.Email == "" {
        return "", fmt.Errorf("email claim is empty")
    }
    
    return claims.Email, nil
}

// 修改AddToken函数
func AddToken(tk *jwt.Token, c echo.Context) error {
    ctx, db := backend.Connect()
    defer db.Close()

    // 生成签名后的token
    signedToken, err := tk.SignedString([]byte("secret"))
    if err != nil {
        return err
    }

    // 直接从token获取email,而不是从上下文
    email, emailErr := GetEmailFromToken(tk)
    if emailErr != nil {
        return fmt.Errorf("error: failed to retrieve email from claim: %w", emailErr)
    }
    
    // 获取过期时间
    claims, ok := tk.Claims.(*UserCustomClaims)
    if !ok {
        return fmt.Errorf("failed to get claims")
    }
    
    expTime := claims.ExpiresAt.Time

    // 插入数据库
    _, err = db.Exec(
        ctx,
        `INSERT INTO sessions (token, data, expiry) VALUES($1, $2, $3)`,
        signedToken, email, expTime,
    )

    if err != nil {
        return fmt.Errorf("error: failed to add usersession: %w", err)
    }
    return nil
}

主文件 - 修改vetLogin函数传递token:

func vetLogin(c echo.Context) error {
    usr := c.FormValue("email")
    pswd := c.FormValue("password")

    rsltErr := accts.CompareUserCreds(usr, pswd)
    if rsltErr != nil {
        return echo.NewHTTPError(http.StatusUnauthorized, rsltErr)
    }

    tk, err := sessionmanager.CreateToken(usr, c)
    if err != nil {
        return echo.NewHTTPError(http.StatusBadRequest, err)
    }

    // 将token传递给AddToken
    err = sessionmanager.AddToken(tk, c)
    if err != nil {
        return echo.NewHTTPError(http.StatusBadRequest, err)
    }

    // 如果需要返回token给客户端
    tokenString, _ := tk.SignedString([]byte("secret"))
    c.Response().Header().Set("Authorization", "Bearer "+tokenString)

    return c.Render(http.StatusOK, "posts", "Add Posts to Your Feed")
}

对于需要从请求头中提取JWT并获取email的场景

func GetEmailFromRequest(c echo.Context) (string, error) {
    // 从Authorization头获取token
    authHeader := c.Request().Header.Get("Authorization")
    if authHeader == "" {
        return "", echo.NewHTTPError(http.StatusUnauthorized, "missing authorization header")
    }
    
    // 移除"Bearer "前缀
    tokenString := strings.TrimPrefix(authHeader, "Bearer ")
    
    // 解析token
    token, err := jwt.ParseWithClaims(tokenString, &sessionmanager.UserCustomClaims{}, 
        func(token *jwt.Token) (interface{}, error) {
            return []byte("secret"), nil
        })
    
    if err != nil {
        return "", echo.NewHTTPError(http.StatusUnauthorized, "invalid token")
    }
    
    if !token.Valid {
        return "", echo.NewHTTPError(http.StatusUnauthorized, "invalid token")
    }
    
    claims, ok := token.Claims.(*sessionmanager.UserCustomClaims)
    if !ok {
        return "", echo.NewHTTPError(http.StatusUnauthorized, "invalid claims")
    }
    
    return claims.Email, nil
}

关键点:

  1. 在创建token后立即使用,此时JWT中间件尚未将token设置到上下文中
  2. 直接从token对象提取claims,而不是依赖上下文
  3. 确保在需要验证token的路由上正确配置JWT中间件
回到顶部