簡體   English   中英

Golang Websocket (Gorilla) 帶 cookie 認證

[英]Golang Websocket (Gorilla) with cookie authentification

我正在嘗試使用大猩猩 websocket 來啟動圖表。 身份驗證中間件通過帶有 JWT 令牌的 cookie 工作。 我通過 HTTP 的所有端點都有效,但 websocket 無效。 在閱讀了很多主題后,比如Gorilla websocket with cookie authentication我發現我的 cookie 是空的,我在 websocket 連接中的上下文也是空的。 我不明白為什么? 誰能解釋我為什么? PS:我已經嘗試從該處理程序中刪除升級程序,並且 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_handler

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()
    }
}

所有其他端點都有效,如果您需要任何其他代碼,請告訴我。 我不想讓我的問題更復雜,因為我認為它很簡單,但我誤解了一些東西(感謝您的幫助和建議。

PS:如果我關閉中間件,一切都會按預期進行。

更新:添加了驗證和生成函數

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 package 中生成的JWT令牌生成/驗證開始。

auth/auth.go文件

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文件

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文件

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文件

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")
}

這是設置邏輯。 這里沒有什么值得一提的。
如果您還需要其他幫助,請告訴我,謝謝!

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM