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

Apollo客户端订阅传递的JWT令牌如何被Django Channels中间件处理

解决方案

方案1:动态拼接令牌到WebSocket查询字符串(适合临时测试)

可以将令牌拼接到WebSocket连接的查询参数中,中间件可以直接读取该参数,支持动态获取最新令牌。

客户端修改

调整WebSocketLink配置,开启lazy模式,将uri改为动态生成的函数,每次建立连接时自动读取最新的令牌拼接:

const wsLink = new WebSocketLink({
  // uri改为函数,每次新建连接时执行获取最新令牌
  uri: async () => {
    const baseWsUri = process.env.VUE_APP_WS_GRAPHQL_URL || "ws://0.0.0.0:8000/ws/graphql/"
    const { value: authStr } = await Storage.get({ key: "auth" });
    if (!authStr) return baseWsUri
    const auth = JSON.parse(authStr)
    return `${baseWsUri}?token=${encodeURIComponent(auth.token)}`
  },
  options: {
    reconnect: true,
    lazy: true, // 延迟初始化连接,有订阅请求时才建立连接
  },
});

如果用户登录/刷新令牌后需要立刻更新连接,手动调用wsLink.subscriptionClient.close()触发自动重连即可,新连接会携带最新的令牌。

后端中间件修改

在中间件中解析查询字符串获取令牌:

from urllib.parse import parse_qs
from django.contrib.auth.models import AnonymousUser

class JwtAuthMiddleware(BaseMiddleware):
    def __init__(self, inner):
        self.inner = inner

    async def __call__(self, scope, receive, send):
        close_old_connections()
        scope["user"] = AnonymousUser()
        
        # 解析查询参数
        query_str = scope.get("query_string", b"").decode()
        query_params = parse_qs(query_str)
        token = query_params.get("token", [None])[0]
        
        if token:
            try:
                # 替换为你自己的JWT解码校验逻辑
                decoded_data = jwt_decode(token)
                scope["user"] = await get_user(email=decoded_data["email"])
            except Exception:
                pass
        
        return await super().__call__(scope, receive, send)

注意:该方案令牌会出现在URL中,可能被服务日志记录,生产环境使用需要做好日志脱敏,更推荐使用方案2。


方案2:在Consumer层完成认证(生产环境推荐)

connectionParams本身就是设计用来传递WebSocket连接的元数据,直接在Consumer层处理认证更符合协议规范,无需修改现有客户端代码。

后端修改

不用调整现有中间件,直接重写你的MyGraphqlWsConsumer的connect方法,读取connection_params完成认证:

from channels_graphql_ws import GraphqlWsConsumer
from django.contrib.auth.models import AnonymousUser

class MyGraphqlWsConsumer(GraphqlWsConsumer):
    # 保留你原来的schema等配置
    async def connect(self):
        await self.accept()
        # 读取Apollo传递的connectionParams
        token = self.connection_params.get("token")
        self.scope["user"] = AnonymousUser()
        
        if token:
            try:
                # 替换为你自己的JWT解码校验逻辑
                decoded_data = jwt_decode(token)
                self.scope["user"] = await get_user(email=decoded_data["email"])
            except Exception:
                # 认证失败可以选择直接关闭连接
                # await self.close(code=4401)
                pass
        
        # 执行父类原有逻辑
        await super().connect()

该方案优势:

  • 无需修改现有客户端代码,你已经配置的connectionParams可以直接复用
  • 令牌不会出现在URL中,安全性更高
  • 每次重连都会自动获取最新的令牌,符合动态取值的需求
  • 符合GraphQL over WebSocket的协议设计规范

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.24 19:45:08