【问题标题】:How to securely send a message to a specific user如何安全地向特定用户发送消息
【发布时间】:2018-09-06 00:32:48
【问题描述】:

我正在使用 ASP.NET MVC 5 和 SignalR。我想向特定用户发送消息。我遵循了this tutorial 中解释的方法(也由this answer 建议)。

我已覆盖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);
    }
}

一切正常。我的问题是关于安全性,这是否是安全网站的正确方法?

根据Introduction to SignalR Security,随机生成的连接ID是SignalR安全的一部分:

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

上述方法是将随机选择的connectionId替换为固定的UserId... 上面的代码有安全问题吗?


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

【问题讨论】:

  • @Hoonman,我已经更新了很多答案。请看一下。

标签: asp.net-mvc signalr asp.net-identity signalr-2


【解决方案1】:

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

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

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

【讨论】:

    【解决方案2】:

    更新时间

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

    使用随机生成的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 安全,请在ChatHub 类上添加[Authorize] 属性。

    [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 方法来绑定通知。另一种方法是在布局页面中使用部分视图作为通知区域。我个人更喜欢在通知区域使用局部视图。

    希望对您有所帮助!

    【讨论】:

    • @Hooman 我已经在一个安全的应用程序中使用它来进行实时用户聊天!
    • 好的!如果您不想使用 InMemory 解决方案,那么只需将用户的连接 ID 存储在数据库中并相应地更改代码!
    • 这不是线程安全的,你没有解释为什么它会更安全。
    • 你仍然没有解释为什么这会(更)安全。
    • 重点是您的答案是“使用此代码,它更安全”,而没有解释为什么它(更)安全。跨度>
    【解决方案3】:

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

    我在ASP.NET SignalR Forum 上发布了同样的问题,他们确认使用固定的 ClientId 作为 connectionId 是一种不太安全的解决方案。如果安全是一个问题,那么Permanent, external storage 是您最好的选择,因为connectionId 是随机生成的,很难猜到。

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

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

    【讨论】:

      猜你喜欢
      • 2019-07-24
      • 1970-01-01
      • 1970-01-01
      • 2012-07-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-02-16
      相关资源
      最近更新 更多