如何在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
相关产品推荐
相关产品推荐

