英文:
Golang Websocket (Gorilla) with cookie authentification
问题
我正在尝试使用gorilla websocket来启动图表。身份验证中间件通过带有JWT令牌的cookie工作。我的所有HTTP端点都可以正常工作,但是websocket却不行。在阅读了很多类似于https://stackoverflow.com/questions/29324251/gorilla-websocket-with-cookie-authentication的主题后,我发现我的cookie为空,而且websocket连接中的上下文也为空。我不明白为什么?有人可以解释一下吗?
附注:我尝试删除处理程序中的升级器,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) //找不到“jwt”键的值
fmt.Println(claims.ID, claims.Name)
cookie, err := c.Cookie("chartJWT") //始终出错,没有cookie
if err != nil {
fmt.Printf("没有cookie,错误:%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) {
// 创建JWT声明,其中包括用户名和到期时间
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。
英文:
I'm trying to use gorilla websocket to start the chart.
Authentification middleware working through cookie with JWT token.
All my endpoints through HTTP works, but websocket doesn't.
After reading a lot of topics like https://stackoverflow.com/questions/29324251/gorilla-websocket-with-cookie-authentication I found that my cookie empty, and my context in the websocket connection also empty. I don't understand why? Can anyone explain me why?
P.S.: I have tried delete upgrader from that handler and cookie and context passed well, but after upgrade connection to websocket protocol it's fails.
Here is my files:
Endpoints:
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()
}
}
All other endpoints works, if you need any other code just let me know. I don't want to make my question more complex, as i think it's pretty simple, but i misunderstand something (
Thanks for any help and advises.
P.S.: if i turn off middleware everything works as expects.
Update:
Added validate and generate funcs
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
}
added signin func where i added cookie with JWT string
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 after help:
The problem was in my Postman setup request where i didn't specify cookie right.
答案1
得分: 1
让我试着帮你找出问题出在哪里。首先,我简化了你的示例,只关注相关部分。如果你需要省略某些内容,请告诉我,我会更新答案。首先,让我从本地的auth
包开始讲解JWT
令牌的生成/验证。
auth/auth.go
文件
package auth
import (
"fmt"
"strings"
"time"
"github.com/golang-jwt/jwt"
)
func ValidateToken(jwtToken string) (*jwt.MapClaims, error) {
// 解析令牌
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
})
// 解析令牌时出错
if err != nil {
return nil, err
}
// 令牌有效
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: 在这里你可以添加与数据库的检查逻辑
//...
// 通过提供加密算法创建一个新的令牌
token := jwt.New(jwt.SigningMethodHS256)
// 设置默认/自定义声明
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
}
// 我不知道你在 handler.Service.LoginUser() 方法中做了什么
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")
}
这是设置逻辑,没有什么值得一提的地方。
如果你还需要其他帮助,请告诉我,谢谢!
英文:
Let me try to help to figure out what was wrong. First, I simplified a little bit your example just to focus only on the relevant parts. If you need something omitted just let me know and I'll update the answer. First, let me start with the JWT
token generation/verification which is made within the local auth
package.
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
}
Now, let's move to the middleware part.
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()
}
}
To be able to upload something in the context you should use c.Set(key, value)
method. Now, let's move to the handlers.
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
}
Missing parts, such as the handler.Service.LoginUser()
method are skipped due to the fact that I don't know what they do. To properly read stuff from context, you've to use the c.MustGet(key)
method.
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!
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论