Управление подключениями SignalR для анонимного пользователя

Я использую SignalR версии 2.1.2 с ASP.Net MVC 5 и NServiceBus и имею следующее требование

Существует страница регистрации (анонимная аутентификация), на которой SignalR используется для отправки уведомлений. Каждая отправка формы будет генерировать новый идентификатор соединения, который необходимо сохранить в коллекции, чтобы я мог отправлять ответ клиенту. Context.User.Identity.Name пуст, следовательно, _connections.Add (name, Context.ConnectionId); не может использоваться в событии концентратора OnConnected (), как указано в этом сообщение

Аналогичная проблема существует на странице входа в систему.

Если есть возможность контролировать ConnectionId, я мог бы преодолеть эту ситуацию, но похоже, что новая версия SignalR избавилась от фабрики соединений.

Я использую кеш Redis, поэтому один из вариантов - написать собственный код управления подключением, чтобы сохранить в нем эти идентификаторы подключения.

Второй вариант - использовать проверку подлинности с помощью форм таким образом, чтобы этим пользователям была назначена «Анонимная роль», которая ограничивает использование анонимных представлений / контроллеров, но дает пользователю «Имя», так что Context.User.Identity.Name является не пустой. Благодаря этому я могу использовать встроенный механизм SignalR для управления идентификаторами соединений за меня.


person nzsai    schedule 31.12.2014    source источник
comment
Я подробно рассказал о нашей реализации в ответе ниже. Спасибо @Punit за ваш вклад.   -  person nzsai    schedule 25.02.2015


Ответы (2)


Используйте проверку подлинности с помощью форм, сохраните федеративный файл cookie, а также сохраните регион концентратора в файле cookie. В коде jQuery SignalR используйте плагин jQuery для чтения HTTP-файла cookie, получения имени региона и подписки на уведомления.

В качестве альтернативы, в вашем .cshtml визуализируйте jQuery с регионом, заполненным из вашей модели представления.

Примечание. Используйте FormsAuthentication.SetAuthCookie, так как это создаст cookie только для HTTP и будет отправляться в вызовах Ajax и не Ajax.

person GeekzSG    schedule 31.12.2014

Это то, что мы сделали в 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);
                    }
                }
            }
        }
    }
}

Объявите статическую переменную SignalRConnectionStore в классе Hub, как показано ниже.

public class ProvisioningHub : Hub
{
    private static ISignalRConnectionStore SignalRConnectionStore;

    public ProvisioningHub(ISignalRConnectionStore signalRConnectionStore)
        : base()
    {
        SignalRConnectionStore = signalRConnectionStore; //Injected using Windsor Castle
    }
}
person nzsai    schedule 25.02.2015
comment
SignalRConnectionStore - это обычный класс списка? не могли бы вы опубликовать код для этого класса SignalRConnectionStore, спасибо - person Mou; 24.03.2015
comment
спасибо за ваш код, но я все еще не понимаю эту функцию OnAuthentication ...... где разместить эту функцию. пожалуйста, опубликуйте полный код для OnAuthentication с кодом контроллера и когда будет вызываться OnAuthentication? предположим, что новый пользователь посетит страницу, тогда будет вызываться OnAuthentication или нет ..... здесь у меня небольшая путаница. - person Mou; 28.03.2015
comment
@Mou, я добавил базовый контроллер и класс контроллера. Сообщите мне, помогло ли это. - person nzsai; 29.03.2015
comment
спасибо за полный код. когда вызовет функцию OnAuthentication ()? вызывает ли он каждый раз, когда контроллер будет вызываться управляемым от базового контроллера. пожалуйста, дайте мне знать. Благодарность - person Mou; 30.03.2015
comment
@Mou, он будет вызываться для каждого вызова метода действия в контроллере, если он наследует этот базовый класс. - person nzsai; 31.03.2015