Как безопасно отправить сообщение конкретному пользователю - PullRequest
0 голосов
/ 06 сентября 2018

Я использую ASP.NET MVC 5 и SignalR. Я хочу отправить сообщение определенному пользователю. Я следовал методу, который описан в в этом уроке (также предлагается в этом ответе ).

Я переопределил IUserIdProvider, чтобы использовать UserId как Идентификатор соединения .

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 , случайно сгенерированный идентификатор соединения является частью безопасности SignalR:

Сервер не обрабатывает запрос от идентификатора соединения, который делает не соответствует имени пользователя. Вряд ли злоумышленник сможет угадать действительный запрос, потому что злоумышленник должен знать пользователя имя и текущий случайно сгенерированный идентификатор соединения.

Приведенный выше подход заменяет случайно выбранный connectionId на фиксированный UserId ... Есть ли какие-либо проблемы безопасности с приведенным выше кодом?


Примечание : я работаю на веб-сайте электронной коммерции, где пользователи должны иметь возможность получать сообщения, даже если они отключены (сообщения будут храниться в БД, и они может читать, как только онлайн).

Ответы [ 2 ]

0 голосов
/ 19 сентября 2018

В документации MS ничего не говорится о соображениях безопасности при объяснении IUserID провайдера , что, на мой взгляд, делает вопрос запутанным ...

Я разместил тот же вопрос на ASP.NET SignalR Forum , и они подтвердили, что использование фиксированного ClientId в качестве connectionId является менее безопасным решением. Если проблема безопасности, то Постоянное внешнее хранилище - ваш лучший выбор, потому что идентификатор соединения генерируется случайным образом, и его трудно угадать.

В случае моего приложения я продолжил использовать подход поставщика IUserID (менее безопасный вариант). Хотя я добавил некоторую проверку на стороне сервера, перед отправкой сообщения:

  1. Очевидно, используя: [Authorize]
  2. Я добавил механизм блокировки и проверю, Отправитель не заблокирован Получателем перед отправкой сообщения.
  3. Я также добавил механизм, который Отправитель может отправлять максимум 10 неотвеченных сообщений на Получатель .
0 голосов
/ 06 сентября 2018

Обновлено

Примечание. Я работаю на веб-сайте электронной коммерции, где пользователи должны иметь возможность получать сообщения, даже если они находятся в автономном режиме (сообщения будут храниться в БД, и их можно будет прочитать, как только они появятся в сети).

Выполните следующие действия для отправки сообщения с использованием случайно сгенерированного идентификатора соединения в защищенном виде:

Сначала добавьте следующие классы для отслеживания идентификаторов соединений пользователя, чтобы понять, подключен ли пользователь к сети или нет

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);
}

Теперь вопрос в том, что если сообщение было отправлено, когда получатель был в автономном режиме?

Хорошо! В этом случае вы можете обработать push-уведомление двумя способами! вызов метода ajax или метода SignalR Hub, как только получатель подключится к сети для привязки уведомлений. Другой метод - частичное представление области уведомлений на странице макета. Лично я предпочитаю использовать частичное представление для области уведомлений.

Надеюсь, это поможет вам!

...