我正在使用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的任何请求 与用户名不匹配。恶意用户不太可能猜到 有效请求,因为恶意用户必须知道该用户 名称和当前随机生成的连接ID。
以上方法是用固定的UserId
替换随机选择的 connectionId ...以上代码是否存在安全问题?
注意:我正在一个电子商务网站上,即使用户离线,用户也需要能够接收消息(消息将存储在数据库中并且他们可以在线阅读)。
答案 0 :(得分:1)
注意:我正在一个电子商务网站上,即使用户处于脱机状态,用户也需要能够接收消息(消息将存储在DB中,并且一旦联机就可以阅读)。
首先添加以下类来跟踪用户的connectionId,以了解用户是在线还是离线
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方法来绑定通知。另一种方法是将局部视图用于布局页面中的通知区域。我个人更喜欢将局部视图用于通知区域。
希望这对您有帮助!
答案 1 :(得分:1)
您在通往正确解决方案的途中进展顺利。唯一的技巧是,应设置安全性,以免欺骗他人的UserId。
例如,我们在SignalR上具有完全相同的场景,但是我们使用JWT令牌中的UserId声明来告诉您您是谁。因此,如果您想接收该人的消息,则需要知道该人的登录凭据。您不能只更改声明中的UserId,因为这样JWT签名将无效,并且您将不再得到身份验证或授权。
所以TL; DR:使用JWT身份验证或带有单向签名的东西,以防止篡改UserId。
答案 2 :(得分:0)
MS文档在解释IUserID provider时没有提及任何安全方面的考虑,我认为这使事情变得混乱...
我在ASP.NET SignalR Forum上发布了相同的问题,他们确认使用固定的ClientId作为connectionId是不太安全的解决方案。如果需要考虑安全性,那么Permanent, external storage是您最好的选择,因为connectionId是随机生成的并且很难猜测。
对于我的应用程序,我继续使用IUserID provider方法(不太安全的选项)。尽管我确实在发送消息之前在服务器端添加了一些验证:
[Authorize]