我正试着用大猩猩网络插座来启动图表. 身份验证中间件通过带有JWT令牌的Cookie工作. 我所有的端点都通过HTTP工作,但WebSocket不行. 在阅读了很多像Gorilla websocket with cookie authentication这样的话题后,我发现我的cookie是空的,我在WebSocket连接中的上下文也是空的.我不明白为什么?有人能给我解释一下为什么吗? 附注:我已经try 从该处理程序中删除Upgrader,Cookie和上下文传递得很好,但在升级到WebSocket协议后失败. 以下是我的文件: 终端:

func (r *router) routes(engine *gin.Engine) {
    engine.Use(r.handler.VerifyUser())

    engine.POST("/signup", r.handler.CreateUser)
    engine.POST("/signin", r.handler.LoginUser)
    engine.GET("/welcome", r.handler.Welcome)
    engine.GET("/logout", r.handler.Logout)

    engine.POST("/ws/createRoom", r.wsHandler.CreateRoom)
    engine.GET("/ws/joinRoom/:roomId", r.wsHandler.JoinRoom)
}

WS_HANDER

func (h *Handler) JoinRoom(c *gin.Context) {
    claims := c.Request.Context().Value("jwt").(models.Claims) //couldn't find value with "jwt" key
    fmt.Println(claims.ID, claims.Name)
    cookie, err := c.Cookie("chartJWT") // allways err no cookie
    if err != nil {
        fmt.Printf("no cookie, error:%v\n", err)
    }
    fmt.Printf("cookie: %+v\n", cookie)
    conn, err := upgrader.Upgrade(c.Writer, c.Request, nil)
    if err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
        return
    }

中间件:

func (h *handler) VerifyUser() gin.HandlerFunc {
    return func(c *gin.Context) {
        notAuth := []string{"/signup", "/signin"}
        requestPath := c.Request.URL.Path

        for _, val := range notAuth {
            if val == requestPath {
                c.Next()
                return
            }
        }

        token, err := c.Cookie("chartJWT")
        if err != nil {
            c.Redirect(http.StatusPermanentRedirect, signinPage)
        }

        claims, ok := validateToken(token)
        if !ok {
            c.JSON(http.StatusBadRequest, gin.H{"error": errors.New("invalid token")})
            return
        }

        c.Request = c.Request.WithContext(context.WithValue(c.Request.Context(), "jwt", *claims))

        c.Next()
    }
}

所有其他端点都可以工作,如果您需要任何其他代码,请让我知道.我不想把我的问题弄得更复杂,因为我认为它很简单,但我误解了一些事情( 感谢您的帮助和建议.

附言:如果我关闭中间件,一切都会正常工作.

最新情况: 添加了验证和生成函数

func validateToken(jwtToken string) (*models.Claims, bool) {
    claims := &models.Claims{}

    token, err := jwt.ParseWithClaims(jwtToken, claims, func(token *jwt.Token) (interface{}, error) {
        return []byte(config.SECRETKEY), nil
    })
    if err != nil {
        return claims, false
    }

    if !token.Valid {
        return claims, false
    }

    return claims, true
}

func (h *handler) generateTokenStringForUser(id, name string) (string, error) {
    // Create the JWT claims, which includes the username and expiry time
    claims := models.Claims{
        ID:   id,
        Name: name,
        RegisteredClaims: jwt.RegisteredClaims{
            Issuer:    id,
            ExpiresAt: jwt.NewNumericDate(time.Now().Add(24 * time.Hour)),
        },
    }

    token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
    tokenString, err := token.SignedString([]byte(config.SECRETKEY))
    return tokenString, err
}

添加了登录函数,其中添加了带有JWT字符串的Cookie

func (h *handler) LoginUser(c *gin.Context) {
    var input models.LoginUserReq

    if err := c.ShouldBindJSON(&input); err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
        return
    }

    res, err := h.Service.LoginUser(context.Background(), &input)
    if err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
        return
    }

    token, err := h.generateTokenStringForUser(res.ID, res.Name)
    if err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
        return
    }

    c.SetCookie("chartJWT", token, 60*60*24, "/", "localhost", false, true)
    c.JSON(http.StatusOK, gin.H{"user": res})
}

帮助后更新: 问题出在我的postman 设置请求中,我没有正确指定Cookie.

推荐答案

让我试着帮你找出哪里出了问题.首先,我稍微简化了您的示例,以便只关注相关部分.如果您需要省略的内容,请让我知道,我会更新答案.首先,让我从本地auth包中进行的JWT令牌生成/验证开始.

auth/auth.go file

package auth

import (
    "fmt"
    "strings"
    "time"

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

func ValidateToken(jwtToken string) (*jwt.MapClaims, error) {
    // parse the token
    token, err := jwt.Parse(strings.Replace(jwtToken, "Bearer ", "", 1), func(token *jwt.Token) (interface{}, error) {
        _, ok := token.Method.(*jwt.SigningMethodHMAC)
        if !ok {
            return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
        }
        return []byte("Abcd1234!!"), nil
    })
    // err while parsing the token
    if err != nil {
        return nil, err
    }
    // token valid
    var claims jwt.MapClaims
    var ok bool
    if claims, ok = token.Claims.(jwt.MapClaims); ok && token.Valid {
        return &claims, nil
    }
    return nil, fmt.Errorf("token not valid")
}

func GenerateToken(username, password string) (string, error) {
    // TODO: here you can add logic to check against a DB
    //...

    // create a new token by providing the cryptographic algorithm
    token := jwt.New(jwt.SigningMethodHS256)

    // set default/custom claims
    claims := token.Claims.(jwt.MapClaims)
    claims["exp"] = time.Now().Add(24 * time.Hour * 3).Unix()
    claims["username"] = username
    claims["password"] = password

    tokenString, err := token.SignedString([]byte("Abcd1234!!"))
    if err != nil {
        return "", err
    }
    return tokenString, nil
}

现在,让我们转到中间件部分.

middlewares/middlewares.go file

package middlewares

import (
    "net/http"

    "websocketauth/auth"

    "github.com/gin-gonic/gin"
)

func VerifyUser() gin.HandlerFunc {
    return func(c *gin.Context) {
        notAuth := []string{"/signin"}
        requestPath := c.Request.URL.Path
        for _, val := range notAuth {
            if val == requestPath {
                c.Next()
                return
            }
        }

        token, err := c.Cookie("chartJWT")
        if err != nil {
            c.Redirect(http.StatusPermanentRedirect, "/signin")
        }

        claims, err := auth.ValidateToken(token)
        if err != nil {
            c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
            return
        }

        c.Set("jwt", *claims)
        c.Next()
    }
}

为了能够在上下文中上传一些东西,你应该使用c.Set(key, value)方法.现在,让我们来看看处理程序.

handlers/handlers.go file

package handlers

import (
    "fmt"
    "net/http"

    "websocketauth/auth"

    "github.com/gin-gonic/gin"
    "github.com/golang-jwt/jwt"
    "github.com/gorilla/websocket"
)

var Upgrader websocket.Upgrader

type LoginUserReq struct {
    Username string `json:"username" binding:"required"`
    Password string `json:"password" binding:"required"`
}

func LoginUser(c *gin.Context) {
    var input LoginUserReq
    if err := c.ShouldBind(&input); err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
        return
    }

    // I don't know what you do within the handler.Service.LoginUser() method

    token, err := auth.GenerateToken(input.Username, input.Password)
    if err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
        return
    }

    c.SetCookie("chartJWT", token, 60*60*24, "/", "localhost", false, true)
    c.JSON(http.StatusOK, gin.H{"user": token})
}

func JoinRoom(c *gin.Context) {
    claims := c.MustGet("jwt").(jwt.MapClaims)
    fmt.Println("username", claims["username"])
    fmt.Println("password", claims["password"])
    ws, err := Upgrader.Upgrade(c.Writer, c.Request, nil)
    if err != nil {
        panic(err)
    }
    chartToken, err := c.Cookie("chartJWT")
    if err != nil {
        panic(err)
    }
    fmt.Println("chartToken", chartToken)
    _ = ws
}

缺少的部分,比如handler.Service.LoginUser()方法,由于我不知道它们是做什么的,所以被跳过了.要正确地从上下文中读取内容,您必须使用c.MustGet(key)方法.

main.go file

package main

import (
    "websocketauth/handlers"
    "websocketauth/middlewares"

    "github.com/gin-gonic/gin"
    "github.com/gorilla/websocket"
)

func main() {
    handlers.Upgrader = websocket.Upgrader{
        ReadBufferSize:  1024,
        WriteBufferSize: 1024,
    }
    gin.SetMode(gin.DebugMode)
    r := gin.Default()

    r.Use(middlewares.VerifyUser())
    r.GET("/join-room", handlers.JoinRoom)
    r.POST("/signin", handlers.LoginUser)

    r.Run(":8000")
}

This is the set up logic. Nothing worth mentioning here.
Let me know if you still need other help, thanks!

Go相关问答推荐

Go PQ驱动程序无法使用默认架构进行查询

GitHub发布Golang子模块

Websocket服务器实现与x/net库trowing 403

exec的可执行决议.命令+路径

确定果朗CSV行中的字节数

如何测试 Zerolog 记录器引发类型错误的日志(log)事件?

无效操作:v > max(类型参数 T 与 > 不可比较)

htmx 表单 + gin 无法正确读取请求正文

Go Programming Language书上的例子server2错了吗?

用于提取 <*n 的正则表达式(其中 n 是一个数字)

从 eBPF LRU 哈希映射中错误驱逐的元素

如何从 Go 中的 `HijackedResponse` 中删除 Cursor Position ANSI 转义码?

甚至用天真的洗牌分配?

Apache Beam 在 Go 中从 PCollection 中 Select 前 N 行

如何使用 Go 代理状态为 OK 的预检请求?

如何仅提取时间作为持续时间

在 Golang 中,如何将接口作为泛型类型与 nil 进行比较?

使用不安全的指针从 [] 字符串中获取值

gopls 为 github.com/Shopify/sarama 返回错误gopls: no packages returned: packages.Load error

如何动态解析 Go Fiber 中的请求正文?