按域隔离 signalR hub 中的用户

Isolate users in signalR hub by domain

我有一个 Web 应用程序,它是一个单一的 IIS 安装(这没有改变),但有一个子域的动态集合。每个子域都有自己的用户帐户。

我 运行 遇到的问题是,当我在上面 运行 signalR 时,它会将所有子域视为相同的域,因此恰好如此的用户拥有相同的域用户名将收到彼此的消息。

这导致域帐户之间出现安全违规问题。

到目前为止,我对此的最佳猜测解决方案具有不同程度的风险和问题。

  1. 每个用户都有自己的群组,群组名由子域名+用户名组成。
    1. 列表项可最大限度地降低碰撞风险,但不会将其删除。
    2. 为域名使用 Guid,并为 guid 保留前 n 个字符进一步降低了风险,但现在对于每个在线用户,我现在都形成了一个组。
  2. 在 owin 启动时,启动一个代表每个域的新中心。
    1. 每次添加子域时,我都必须重新启动应用程序以添加新的集线器。现在,我不需要做任何事情来添加 DNS 支持通配符的子域,并且 IIS 中的主机头是空白的。除了 SignalR 中缺少子域感知外,所有工作正常。
  3. 构建自定义集线器 class,使客户端集合域感知,就像应用程序的其余部分一样。
    1. 这似乎是最干净的,但到目前为止,也是最耗时的。它还带来了最高的错误风险,因为我将不得不在 TDD 单元测试之外编写更多的 QA 测试。
  4. 最后一个选项,不要使用 SignalR,构建我自己的长轮询 API。
    1. 这是最难接受的,因为它是最高带宽和最暴露的进程。对我们目标用户的一项基本调查表明,他们正在使用支持 websocket 的浏览器,那么我们为什么要故意增加带宽或制造新的延迟。

要查看此失败,只需在 ASP.NET/SignalR 获取简单的聊天演示,然后 运行 在本地计算机上使用两种不同的浏览器(我的是 FF 和 IE)核心测试),一个调用 http:\localhost,另一个调用 http:\yourcomputername。您将需要 IIS 而不是 IIS Express 来进行正确的测试。

我的 2 美分:构建您自己的 IUserIdProvider 实现,从那里应该可以很容易地检查每个请求并生成跨多个域的唯一用户 ID,您可以这样 return SignalR 会知道将每个请求正确关联到谁。这将是一个简单且非侵入性的解决方案。您可以查看 here 了解更多详情。

我知道这有点晚了,但是我也遇到过这个问题并且我已经使用组解决了它,但是他们的方式是我自己实现 IHub 然后设置 Clients 被称为将值包装在我自己的 IHubCallerConnectionContext<dynamic> 实现中,然后使用一个键来隔离使用已经可用的方法进行的所有调用。这是 class 的示例:

internal class ClientsDatabaseIsolator : IHubCallerConnectionContext<object>
{
    private readonly string _database;
    private readonly IHubCallerConnectionContext<dynamic> _clients;

    public ClientsDatabaseIsolator(string database, IHubCallerConnectionContext<dynamic> clients)
    {
        if (database == null) throw new ArgumentNullException(nameof(database));
        this._database = database;
        this._clients = clients;
    }

    private string PrefixDatabase(string group)
    {
        return string.Concat(_database, ".", group);
    }

    public dynamic AllExcept(params string[] excludeConnectionIds)
    {
        return _clients.Group(_database, excludeConnectionIds);
    }

    public dynamic Client(string connectionId)
    {
        return _clients.Client(connectionId);
    }

    public dynamic Clients(IList<string> connectionIds)
    {
        return _clients.Clients(connectionIds);
    }

    public dynamic Group(string groupName, params string[] excludeConnectionIds)
    {
        return _clients.Group(PrefixDatabase(groupName), excludeConnectionIds);
    }

    public dynamic Groups(IList<string> groupNames, params string[] excludeConnectionIds)
    {
        return _clients.Groups(groupNames.Select(PrefixDatabase).ToList(), excludeConnectionIds);
    }

    public dynamic User(string userId)
    {
        return _clients.User(userId);
    }

    public dynamic Users(IList<string> userIds)
    {
        return _clients.Users(userIds);
    }

    public dynamic All
    {
        get { return _clients.Group(_database); }
    }

    public dynamic OthersInGroup(string groupName)
    {
        return _clients.OthersInGroup(PrefixDatabase(groupName));
    }

    public dynamic OthersInGroups(IList<string> groupNames)
    {
        return _clients.OthersInGroups(groupNames.Select(PrefixDatabase).ToList());
    }

    public dynamic Caller
    {
        get { return _clients.Caller; }
    }

    public dynamic CallerState
    {
        get { return _clients.CallerState; }
    }

    public dynamic Others
    {
        get { return _clients.OthersInGroup(_database); }
    }
}

然后在 OnConnected 我将连接添加到 _database

现在在我的集线器中,当我调用 Clients.All.Send("message") 时,它实际上只会将消息发送到创建 ClientsDatabaseIsolator 时指定的组,就像那样调用 Clients.Group(database).Send("message")不必考虑它。我不确定这是否是最佳解决方案,但它对我们有用。