按域隔离 signalR hub 中的用户
Isolate users in signalR hub by domain
我有一个 Web 应用程序,它是一个单一的 IIS 安装(这没有改变),但有一个子域的动态集合。每个子域都有自己的用户帐户。
我 运行 遇到的问题是,当我在上面 运行 signalR 时,它会将所有子域视为相同的域,因此恰好如此的用户拥有相同的域用户名将收到彼此的消息。
这导致域帐户之间出现安全违规问题。
到目前为止,我对此的最佳猜测解决方案具有不同程度的风险和问题。
- 每个用户都有自己的群组,群组名由子域名+用户名组成。
- 列表项可最大限度地降低碰撞风险,但不会将其删除。
- 为域名使用 Guid,并为 guid 保留前 n 个字符进一步降低了风险,但现在对于每个在线用户,我现在都形成了一个组。
- 在 owin 启动时,启动一个代表每个域的新中心。
- 每次添加子域时,我都必须重新启动应用程序以添加新的集线器。现在,我不需要做任何事情来添加 DNS 支持通配符的子域,并且 IIS 中的主机头是空白的。除了 SignalR 中缺少子域感知外,所有工作正常。
- 构建自定义集线器 class,使客户端集合域感知,就像应用程序的其余部分一样。
- 这似乎是最干净的,但到目前为止,也是最耗时的。它还带来了最高的错误风险,因为我将不得不在 TDD 单元测试之外编写更多的 QA 测试。
- 最后一个选项,不要使用 SignalR,构建我自己的长轮询 API。
- 这是最难接受的,因为它是最高带宽和最暴露的进程。对我们目标用户的一项基本调查表明,他们正在使用支持 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")
不必考虑它。我不确定这是否是最佳解决方案,但它对我们有用。
我有一个 Web 应用程序,它是一个单一的 IIS 安装(这没有改变),但有一个子域的动态集合。每个子域都有自己的用户帐户。
我 运行 遇到的问题是,当我在上面 运行 signalR 时,它会将所有子域视为相同的域,因此恰好如此的用户拥有相同的域用户名将收到彼此的消息。
这导致域帐户之间出现安全违规问题。
到目前为止,我对此的最佳猜测解决方案具有不同程度的风险和问题。
- 每个用户都有自己的群组,群组名由子域名+用户名组成。
- 列表项可最大限度地降低碰撞风险,但不会将其删除。
- 为域名使用 Guid,并为 guid 保留前 n 个字符进一步降低了风险,但现在对于每个在线用户,我现在都形成了一个组。
- 在 owin 启动时,启动一个代表每个域的新中心。
- 每次添加子域时,我都必须重新启动应用程序以添加新的集线器。现在,我不需要做任何事情来添加 DNS 支持通配符的子域,并且 IIS 中的主机头是空白的。除了 SignalR 中缺少子域感知外,所有工作正常。
- 构建自定义集线器 class,使客户端集合域感知,就像应用程序的其余部分一样。
- 这似乎是最干净的,但到目前为止,也是最耗时的。它还带来了最高的错误风险,因为我将不得不在 TDD 单元测试之外编写更多的 QA 测试。
- 最后一个选项,不要使用 SignalR,构建我自己的长轮询 API。
- 这是最难接受的,因为它是最高带宽和最暴露的进程。对我们目标用户的一项基本调查表明,他们正在使用支持 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")
不必考虑它。我不确定这是否是最佳解决方案,但它对我们有用。