You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在gqlgen中统一验证WebSocket与HTTP请求的同一解析器

统一处理gqlgen中HTTP与WebSocket的身份验证

核心思路

把身份验证逻辑抽成公共函数,分别在HTTP中间件和WebSocket的InitFunc里调用该函数完成校验,再将验证后的用户信息注入请求上下文(context.Context)。后续所有解析器都能从上下文中统一获取用户信息,不用区分请求类型。

步骤实现

1. 封装公共验证函数

先写一个通用的token校验函数,负责验证token有效性并返回用户信息:

// 自定义用户结构体,按需添加字段
type User struct {
    ID    string
    Email string
}

// ValidateToken 公共验证逻辑:接收token,返回用户信息或错误
func ValidateToken(token string) (*User, error) {
    // 这里替换成你的实际验证逻辑(比如JWT解析、数据库校验等)
    if token == "" {
        return nil, errors.New("token不能为空")
    }
    // 模拟验证通过,返回用户数据
    return &User{ID: "123", Email: "user@example.com"}, nil
}

2. 改造HTTP验证中间件

让原有的HTTP中间件调用公共验证函数,把用户信息存入上下文:

func authMiddlewareFunc() func(http.Handler) http.Handler {
    return func(next http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            // 从请求头取token(根据你的实际字段调整,比如"Authorization"或"Access-Token")
            token := r.Header.Get("Access-Token")
            user, err := ValidateToken(token)
            if err != nil {
                http.Error(w, err.Error(), http.StatusUnauthorized)
                return
            }
            // 将用户信息注入context
            ctx := context.WithValue(r.Context(), "user", user)
            next.ServeHTTP(w, r.WithContext(ctx))
        })
    }
}

3. 改造WebSocket的InitFunc

在WebSocket初始化流程中调用同一个公共验证函数,同样把用户信息存入上下文:

srv.AddTransport(&transport.Websocket{
    KeepAlivePingInterval: 10 * time.Second,
    Upgrader: websocket.Upgrader{
        CheckOrigin: func(req *http.Request) bool {
            return true
        },
    },
    InitFunc: func(ctx context.Context, initPayload transport.InitPayload) (context.Context, *transport.InitPayload, error) {
        // 从初始化payload中取token(根据客户端传参方式调整)
        token, ok := initPayload["token"].(string)
        if !ok {
            return ctx, nil, errors.New("缺少token参数")
        }
        // 调用公共验证函数
        user, err := ValidateToken(token)
        if err != nil {
            return ctx, nil, err
        }
        // 将用户信息注入context
        newCtx := context.WithValue(ctx, "user", user)
        // 按需返回给客户端的初始化响应,不需要则传nil
        return newCtx, nil, nil
    },
})

4. 解析器中统一获取用户信息

不管是Query还是Subscription解析器,直接从上下文拿已验证的用户信息:

// 示例Query解析器
func (r *queryResolver) GetUser(ctx context.Context, id string) (*User, error) {
    currentUser, ok := ctx.Value("user").(*User)
    if !ok {
        return nil, errors.New("未授权访问")
    }
    // 后续业务逻辑...
    return &User{ID: id, Email: "target@example.com"}, nil
}

// 示例Subscription解析器
func (r *subscriptionResolver) NewMessage(ctx context.Context) (<-chan *Message, error) {
    currentUser, ok := ctx.Value("user").(*User)
    if !ok {
        return nil, errors.New("未授权访问")
    }
    // 后续订阅逻辑...
    ch := make(chan *Message)
    go func() {
        ch <- &Message{Content: "新消息", SenderID: currentUser.ID}
    }()
    return ch, nil
}

5. 启用HTTP中间件

取消注释你代码中的路由分组,开启HTTP请求的验证:

router.Group(func(r chi.Router) {
    r.Use(authMiddlewareFunc())
    r.Handle(resolverPath, srv)
})

关键优势

  • 逻辑复用:验证逻辑只维护一份,避免重复代码
  • 上下文统一:解析器无需判断请求类型,直接从上下文取用户数据
  • 易扩展:后续修改验证逻辑只需调整公共函数,不用改动多处

内容的提问来源于stack exchange,提问作者Christina Leuci

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.15 01:30:55