7

我正在使用 ASP.NET MVC 5 和 SignalR。我想向特定用户发送消息。我遵循了本教程中解释的方法(也由这个答案建议)。

我已覆盖IUserIdProvider, 以UserId用作connectionId

public class SignalRUserIdProvider : IUserIdProvider
{
    public string GetUserId(IRequest request)
    {
        // use: UserId as connectionId
        return Convert.ToString(request.User.Identity.GetUserId<int>());
    }
}

我已经对我的应用程序进行了更改以Startup使用上述自定义提供程序:

public partial class Startup
{
    public void Configuration(IAppBuilder app)
    {
        var idProvider = new SignalRUserIdProvider();
        GlobalHost.DependencyResolver.Register(typeof(IUserIdProvider), () => idProvider);
        ConfigureAuth(app);
        app.MapSignalR();
    }
}

现在我的客户端可以将目标ClientID传递给集线器,集线器只会将消息转发给请求的客户端,这是我的集线器:

[Authorize] 
public class ChatHub : Hub
{
    public void Send(string message, string destClientId)
    {
        Clients.User(destClientId).messageReceived(Context.User.Identity.Name + " says: " + message);
    }
}

这工作得很好。我的问题是关于安全性,这是否是安全网站的正确方法?

根据SignalR Security 简介,随机生成的连接 id 是 SignalR 安全性的一部分:

服务器不会处理来自与用户名不匹配的连接 ID 的任何请求。恶意用户不太可能猜到有效请求,因为恶意用户必须知道用户名和当前随机生成的连接 ID。

上面的方法是用固定的替换随机选择的connectionIdUserId ......上面的代码有什么安全问题吗?


注意:我正在一个电子商务网站上工作,即使用户处于离线状态,用户也需要能够接收消息(消息将存储在数据库中,一旦在线,他们就可以阅读)。

4

3 回答 3

1

更新于

注意:我在一个电子商务网站上工作,即使用户处于离线状态,用户也需要能够接收消息(消息将存储在数据库中,一旦在线,他们就可以阅读)。

执行以下操作以安全方式使用随机生成的 connectionId 发送消息:

首先添加以下类来跟踪用户的connectionIds来了解用户是在线还是离线

public static class ChatHubUserHandler
{
    public static ConcurrentDictionary<string, ChatHubConnectionViewModel> ConnectedIds =
        new ConcurrentDictionary<string, ChatHubConnectionViewModel>(StringComparer.InvariantCultureIgnoreCase);
}

public class ChatHubConnectionViewModel
{
    public string UserName { get; set; }
    public HashSet<string> UserConnectionIds { get; set; }
}

配置ChatHub如下

使类上的ChatHub安全添加[Authorize]属性ChatHub

[Authorize]
public class ChatHub : Hub
{
    private string UserName => Context.User.Identity.Name;
    private string ConnectionId => Context.ConnectionId;

    // Whenever a user will be online randomly generated connectionId for
    // him be stored here.Here I am storing this in Memory, if you want you
    // can store it on database too.
    public override Task OnConnected()
    {

        var user = ChatHubUserHandler.ConnectedIds.GetOrAdd(UserName, _ => new ChatHubConnectionViewModel
        {
            UserName = UserName,
            UserConnectionIds = new HashSet<string>()
        });

        lock (user.UserConnectionIds)
        {
            user.UserConnectionIds.Add(ConnectionId);
        }

        return base.OnConnected();
    }


    // Whenever a user will be offline his connectionId id will be
    // removed from the collection of loggedIn users.

    public override Task OnDisconnected(bool stopCalled)
    {
        ChatHubConnectionViewModel user;
        ChatHubUserHandler.ConnectedIds.TryGetValue(UserName, out user);

        if (user != null)
        {
            lock (user.UserConnectionIds)
            {
                user.UserConnectionIds.RemoveWhere(cid => cid.Equals(ConnectionId));
                if (!user.UserConnectionIds.Any())
                {
                    ChatHubUserHandler.ConnectedIds.TryRemove(UserName, out user);
                }
            }
        }

        return base.OnDisconnected(stopCalled);
    }
}

现在使用以下模型类将消息存储到数据库。您还可以根据您的确切需要自定义消息类别。

public class Message
{

    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public long MessageId { get; set; }

    [ForeignKey("Sender")]
    public string SenderId { get; set; }

    [ForeignKey("Receiver")]
    public string ReceiverId { get; set; }

    [Required]
    [DataType(DataType.MultilineText)]
    public string MessageBody { get; set; }
    public DateTime MessageSentAt { get; set; }
    public bool IsRead { get; set; }


    public User Sender { get; set; }
    public User Receiver { get; set; }
}

然后在消息控制器中:

这只是一个示例代码。您可以根据您的确切需要自定义代码。

[HttpPost]
public async Task<ActionResult> SendMessage(string messageBody, string receiverAspNetUserId)
{
      string loggedInUserId = User.Identity.GetUserId();
      Message message = new Message()
      {
            SenderId = loggedInUserId,
            ReceiverId = receiverAspNetUserId,
            MessageBody = messageBody,
            MessageSentAt = DateTime.UtcNow
      };

      _dbContext.Messages.Add(message);
      _dbContext.SaveChangesAsync();


      // Check here if the receiver is currently logged in. If logged in,
      // send push notification. Send your desired content as parameter
      // to sendPushNotification method.

      if(ChatHubUserHandler.ConnectedUsers.TryGetValue(receiverAspNetUserId, out ChatHubConnectionViewModel connectedUser))
      {
            List<string> userConnectionIds = connectedUser.UserConnectionIds.ToList();
            if (userConnectionIds.Count > 0)
            {
                var chatHubContext = GlobalHost.ConnectionManager.GetHubContext<ChatHub>();
                chatHubContext.Clients.Clients(userConnectionIds).sendPushNotification();
            }
      }

      return Json(true);
}

现在的问题是,如果在接收方离线时发送消息怎么办?

好的!在这种情况下,您可以通过两种方式处理推送通知!接收者在线后立即调用 ajax 方法或 SignalR Hub 方法来绑定通知。另一种方法是在布局页面中使用部分视图作为通知区域。我个人更喜欢对通知区域使用局部视图。

希望对你有帮助!

于 2018-09-06T02:54:55.667 回答
1

您正在寻找正确的解决方案。唯一的诀窍是您的安全性应该设置为不能欺骗他人的 UserId。

例如,我们与 SignalR 有完全相同的场景,但我们使用来自 JWT 令牌的 UserId 声明来告诉您您是谁。因此,如果您想接收他的消息,则需要知道该人的登录凭据。您不能只更改声明中的 UserId,因为这样 JWT 签名将无效,您将不再获得身份验证或授权。

所以 TL;DR:使用 JWT 身份验证或具有单向签名的东西,以防止篡改 UserId。

于 2020-11-02T12:34:54.993 回答
0

在解释IUserID provider时,MS 文档没有提到任何有关安全考虑的内容,在我看来,这让事情变得令人困惑......

我在ASP.NET SignalR Forum上发布了相同的问题,他们确认使用固定 ClientId 作为 connectionId 是一种不太安全的解决方案。如果安全是一个问题,那么永久的外部存储是您最好的选择,因为 connectionId 是随机生成的并且难以猜测。

对于我的应用程序,我继续使用IUserID 提供程序方法(不太安全的选项)。虽然我确实在服务器端添加了一些验证,但在发送消息之前:

  1. 显然使用:[Authorize]
  2. 我添加了一个阻塞机制,在发送消息之前会验证Sender没有被Receiver阻塞。
  3. 我还添加了一种机制,即Sender最多可以向Receiver发送 10 条未回复的消息。
于 2018-09-19T00:01:37.203 回答