【问题标题】:Managing SignalR connections for Anonymous user为匿名用户管理 SignalR 连接
【发布时间】:2014-12-31 02:01:10
【问题描述】:

我正在使用带有 ASP.Net MVC 5 和 NServiceBus 的 SignalR 版本 2.1.2,并且有以下要求

有一个注册页面(匿名身份验证),其中 SignalR 用于发送通知。每个表单提交都会生成一个新的连接 ID,需要将其保存在一个集合中,以便我可以向客户端发送响应。 Context.User.Identity.Name 为空,因此 _connections.Add(name, Context.ConnectionId);不能在此 post 中给出的 OnConnected() 集线器事件中使用

登录页面也存在类似问题。

如果有可能控制 ConnectionId,那么我可以克服这种情况,但看起来新版本的 SignalR 已经摆脱了连接工厂。

我正在使用 Redis 缓存,因此一种选择是编写我自己的连接管理代码以将这些连接 ID 保存在其中。

第二个选项是以这样一种方式使用表单身份验证,即为这些用户分配一个“匿名角色”,这将使用限制为匿名视图/控制器,但为用户提供一个“名称”,以便 Context.User.Identity .Name 不为空。有了这个,我可以使用内置的 SignalR 机制来为我管理连接 ID。

【问题讨论】:

  • 我在下面的答案中给出了我们实施的细节。感谢@Punit 的投入。

标签: c# asp.net-mvc signalr anonymous


【解决方案1】:

这就是我们在 BaseAnonymousController 中所做的

public class BaseAnonymousController : Controller
{
    protected override void OnAuthentication(System.Web.Mvc.Filters.AuthenticationContext filterContext)
    {
        if (filterContext.Controller.GetType().Name == "AccountController" && filterContext.ActionDescriptor.ActionName == "login")
        {
            Guid result;
            if (!string.IsNullOrEmpty(SessionVariables.UserId) && Guid.TryParse(SessionVariables.UserId, out result))
            {
                //Already a anonymous user, so good to go.
            }
            else
            {
                //Seems to be a logged in a user. So, clear the session
                Session.Clear();
            }
        }

        //Perform a false authentication for anonymous users (signup, login, activation etc. views/actions) so that SignalR will have a user name to manage its connections
        if (!string.IsNullOrEmpty(SessionVariables.UserId))
        {
            filterContext.HttpContext.User = new CustomPrincipal(new CustomIdentity(SessionVariables.UserId, "Anonymous"));
        }
        else
        {
            string userName = Guid.NewGuid().ToString();
            filterContext.HttpContext.User = new CustomPrincipal(new CustomIdentity(userName, "Anonymous"));
            FormsAuthentication.SetAuthCookie(userName, false);
            SessionVariables.UserId = userName;
        }

        base.OnAuthentication(filterContext);
    }
}

并使用这个类作为所有匿名控制器的基类。

public class AccountController : BaseAnonymousController
{
    [AllowAnonymous]
    public ActionResult Signup()
    {
        //Your code
    }

    [AllowAnonymous]
    public ActionResult Login()
    {
        //Your code
    }

    [AllowAnonymous]
    public ActionResult ForgotPassword()
    {
        //Your code
    }

    [AllowAnonymous]
    public ActionResult ForgotUsername()
    {
        //Your code
    }
}

在 SignalR 集线器中(没有比 SignalR 文档中的特别之处)

public override Task OnConnected()
    {
        SignalRConnectionStore.Add(Context.User.Identity.Name, Context.ConnectionId);

        return base.OnConnected();
    }

    public override Task OnReconnected()
    {
        string name = Context.User.Identity.Name;
        //Add the connection id if it is not in it 
        if (!SignalRConnectionStore.GetConnections(name).Contains(Context.ConnectionId))
        {
            SignalRConnectionStore.Add(name, Context.ConnectionId);
        }

        return base.OnReconnected();
    }

    public override Task OnDisconnected(bool stopCalled)
    {
        SignalRConnectionStore.Remove(Context.User.Identity.Name, Context.ConnectionId);

        return base.OnDisconnected(stopCalled);
    }

这适用于匿名用户和经过身份验证的用户。

SignalRConnectionStore 类和接口

public interface ISignalRConnectionStore
{
    int Count { get; }
    void Add(string userName, string connectionId);
    IEnumerable<string> GetConnections(string userName);
    void Remove(string userName, string connectionId);
}

internal class SignalRConnectionStore : ISignalRConnectionStore
{
    private readonly Dictionary<string, HashSet<string>> _connections = new Dictionary<string, HashSet<string>>();

    public int Count
    {
        get
        {
            return _connections.Count;
        }
    }

    public void Add(string userName, string connectionId)
    {
        if (!string.IsNullOrEmpty(userName) && !string.IsNullOrEmpty(connectionId))
        {
            lock (_connections)
            {
                HashSet<string> connections;
                if (!_connections.TryGetValue(userName, out connections))
                {
                    connections = new HashSet<string>();
                    _connections.Add(userName, connections);
                }

                lock (connections)
                {
                    connections.Add(connectionId);
                }
            }
        }
    }

    public IEnumerable<string> GetConnections(string userName)
    {
        if (!string.IsNullOrEmpty(userName))
        {
            HashSet<string> connections;
            if (_connections.TryGetValue(userName, out connections))
            {
                return connections;
            }
        }

        return Enumerable.Empty<string>();
    }

    public void Remove(string userName, string connectionId)
    {
        if (!string.IsNullOrEmpty(userName) && !string.IsNullOrEmpty(connectionId))
        {
            lock (_connections)
            {
                HashSet<string> connections;
                if (!_connections.TryGetValue(userName, out connections))
                {
                    return;
                }

                lock (connections)
                {
                    connections.Remove(connectionId);

                    if (connections.Count == 0)
                    {
                        _connections.Remove(userName);
                    }
                }
            }
        }
    }
}

在 Hub 类中声明一个 SignalRConnectionStore 的静态变量,如下所示。

public class ProvisioningHub : Hub
{
    private static ISignalRConnectionStore SignalRConnectionStore;

    public ProvisioningHub(ISignalRConnectionStore signalRConnectionStore)
        : base()
    {
        SignalRConnectionStore = signalRConnectionStore; //Injected using Windsor Castle
    }
}

【讨论】:

  • SignalRConnectionStore 是普通的列表类吗?你介意发布这个类 SignalRConnectionStore 的代码吗?谢谢
  • 感谢您的代码,但我仍然不明白这个 OnAuthentication 函数......在哪里放置这个函数。 plzz 会用控制器代码发布 OnAuthentication 的完整代码以及何时调用 OnAuthentication 吗?假设一个新用户访问一个页面,那么 OnAuthentication 是否会被调用.....这里我有点困惑。
  • @Mou,我添加了基本控制器和控制器类。如果有帮助,请告诉我。
  • 感谢您提供完整的代码。 OnAuthentication() 函数何时调用?每次从基本控制器驱动控制器时都会调用它。请告诉我。谢谢
  • @Mou,如果它继承了这个基类,控制器中的每个action方法调用都会调用它。
【解决方案2】:

使用表单身份验证,存储联合 Cookie 并将中心区域也存储在 cookie 中。 在 SignalR jQuery 代码中,使用 jQuery 插件读取 HTTP cookie 并获取区域名称并订阅通知。

或者,在您的 .cshtml 中,使用从您的视图模型填充的区域呈现 jQuery。

注意:使用FormsAuthentication.SetAuthCookie,因为这将创建仅 HTTP cookie,并将在 Ajax 和非 Ajax 调用中发送。

【讨论】:

    猜你喜欢
    • 2018-09-10
    • 2018-12-24
    • 2016-01-21
    • 1970-01-01
    • 2013-04-03
    • 2021-07-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多