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

基于HotChocolate的GraphQL租户级订阅安全实现问题

安全实现Kafka消息的租户隔离推送方案

核心思路是完全复用现有认证链路的租户ID提取逻辑,从后端连接上下文而非客户端输入获取租户ID,彻底避免客户端篡改风险,具体实现步骤如下:

1. 在WebSocket连接建立时绑定租户ID

HotChocolate的订阅基于WebSocket,连接建立时会携带前端的Auth0 Token。复用你现有GraphQL查询的Token解析逻辑,从请求上下文提取租户ID并绑定到连接会话中:

builder.Services.AddGraphQLServer()
    .AddSubscriptionType<Subscription>()
    .ConfigureSubscription(options =>
    {
        options.OnConnectAsync = async context =>
        {
            // 复用现有从Token提取租户ID的逻辑
            var httpContext = context.GetHttpContext();
            var tenantId = httpContext.User.Claims.FirstOrDefault(c => c.Type == "tenant_id")?.Value;
            
            if (string.IsNullOrEmpty(tenantId))
            {
                throw new UnauthorizedAccessException("无效Token或缺失租户ID");
            }
            // 将租户ID存入连接上下文,后续订阅逻辑可直接读取
            context.ConnectionData["TenantId"] = tenantId;
            return Task.CompletedTask;
        };
    });

2. 实现租户隔离的消息推送

方式一:按租户ID拆分推送主题

在Kafka消费服务中,将消息推送到与租户ID绑定的专属主题,确保只有对应租户的连接能收到:

public class KafkaConsumerHostedService : BackgroundService
{
    private readonly ITopicEventSender _eventSender;

    public KafkaConsumerHostedService(ITopicEventSender eventSender)
    {
        _eventSender = eventSender;
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        // Kafka消息消费逻辑
        while (!stoppingToken.IsCancellationRequested)
        {
            var kafkaMessage = ConsumeMessageFromKafka();
            // 推送至租户专属主题
            await _eventSender.SendAsync($"tenant-{kafkaMessage.TenantId}-events", kafkaMessage);
        }
    }
}

前端订阅时无需指定租户ID,后端自动从连接上下文获取租户ID,映射到对应主题:

public class Subscription
{
    [Subscribe]
    public KafkaEvent OnTenantEvent(
        [EventMessage] KafkaEvent message,
        [ConnectionData] string tenantId,
        [Topic] string topic)
    {
        // 这里的topic由后端自动生成,客户端无需感知
        return message;
    }
}

方式二:使用全局状态过滤消息

如果不想拆分主题,可在订阅字段中通过全局状态提取租户ID,过滤仅匹配当前租户的消息:

builder.Services.AddGraphQLServer()
    .AddSubscriptionType<Subscription>()
    // 注册全局租户ID状态
    .AddGlobalState("TenantId", context =>
    {
        var httpContext = context.GetHttpContext();
        return httpContext.User.Claims.FirstOrDefault(c => c.Type == "tenant_id")?.Value;
    });

订阅字段中直接注入全局状态做过滤:

public class Subscription
{
    [Subscribe]
    [Topic("kafka-global-events")]
    public KafkaEvent OnKafkaEvent(
        [EventMessage] KafkaEvent message,
        [GlobalState("TenantId")] string tenantId)
    {
        // 仅返回当前租户的消息
        return message.TenantId == tenantId ? message : null;
    }
}

3. 关键安全保障

  • 完全复用现有认证逻辑:租户ID从Auth0 Token的Claims中提取,客户端无法篡改Token内容
  • 无客户端输入依赖:全程无需客户端传递租户ID,彻底消除篡改入口
  • 连接级隔离:每个WebSocket会话绑定唯一租户ID,消息推送精准匹配

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.01 09:50:37