Heim  >  Artikel  >  Backend-Entwicklung  >  Golang Websocket (Gorilla) mit Cookie-Authentifizierung

Golang Websocket (Gorilla) mit Cookie-Authentifizierung

WBOY
WBOYnach vorne
2024-02-11 18:03:081209Durchsuche

带 cookie 身份验证的 Golang Websocket (Gorilla)

In der Webentwicklung ist die Authentifizierung ein wesentliches Merkmal, und die Cookie-basierte Authentifizierung ist eine gängige Methode. Als effiziente und prägnante Programmiersprache verfügt Golang über leistungsstarke Webentwicklungsfunktionen. In diesem Artikel erfahren Sie, wie Sie mit dem Gorilla-Toolkit die Websocket-Funktion mit Cookie-Authentifizierung in Golang implementieren und so Ihre Anwendung sicherer und zuverlässiger machen. Egal, ob Sie ein Golang-Anfänger oder ein erfahrener Entwickler sind, dieser Artikel kann Ihnen den schnellen Einstieg erleichtern. Lass uns einen Blick darauf werfen!

Frageninhalt

Ich versuche, mit Gorilla Websocket ein Diagramm zu starten. Die Authentifizierungs-Middleware funktioniert über Cookies und JWT-Tokens. Alle meine Endpunkte über http funktionieren, Websocket jedoch nicht. Nachdem ich viele Themen wie Gorilla WebSocket mit Cookie-Authentifizierung gelesen hatte, stellte ich fest, dass meine Cookies leer waren und der Kontext in der WebSocket-Verbindung ebenfalls leer war. Ich verstehe nicht warum? Kann jemand erklären, warum? p.s.: Ich habe versucht, das Upgrade von diesem Handler zu entfernen, und das Cookie und der Kontext kamen erfolgreich durch, aber nach dem Upgrade der Verbindung auf das Websocket-Protokoll schlug es fehl. Das ist meine Datei: Endpunkt:

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
    }

Middleware:

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

Alle anderen Endpunkte funktionieren. Wenn Sie anderen Code benötigen, lassen Sie es mich bitte wissen. Ich möchte mein Problem nicht komplizierter machen, weil ich dachte, es sei einfach, aber ich habe etwas falsch verstanden ( Vielen Dank für Ihre Hilfe und Ihren Rat.

ps.s.: Wenn ich die Middleware ausschalte, funktioniert alles wie erwartet.

Update: Validierungs- und Generierungsfunktionen hinzugefügt

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
}

Anmeldefunktion hinzugefügt, bei der ich Cookies mit JWT-String hinzugefügt habe

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

Update nach Hilfe: Das Problem bestand darin, dass ich in meiner Postman-Setup-Anfrage das Cookie nicht richtig angegeben habe.

Lösung

Lassen Sie mich versuchen, Ihnen bei der Lösung des Problems zu helfen. Zunächst habe ich Ihr Beispiel etwas vereinfacht, um mich nur auf die relevanten Teile zu konzentrieren. Wenn Sie etwas auslassen müssen, lassen Sie es mich bitte wissen und ich werde die Antwort aktualisieren. Lassen Sie mich zunächst mit der lokalen auth 包中进行的 jwt Token-Generierung/-Validierung beginnen.

auth/auth.go Dateien

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
}

Jetzt kommen wir zum Middleware-Teil.

middlewares/middlewares.go Dateien

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

Um etwas im Kontext hochladen zu können, sollten Sie die c.set(key, value)-Methode verwenden. Kommen wir nun zu den Handlern.

handlers/handlers.go Dateien

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
}

Fehlende Teile wie handler.service.loginuser() 方法。要正确地从上下文中读取内容,您必须使用 c.mustget(key) Methoden werden übersprungen, da ich nicht weiß, was sie tun.

main.go Dateien

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

Dies ist die Setup-Logik. Hier gibt es nichts Erwähnenswertes.
Wenn Sie weitere Hilfe benötigen, lassen Sie es mich bitte wissen, vielen Dank!

Das obige ist der detaillierte Inhalt vonGolang Websocket (Gorilla) mit Cookie-Authentifizierung. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Dieser Artikel ist reproduziert unter:stackoverflow.com. Bei Verstößen wenden Sie sich bitte an admin@php.cn löschen