SignalR:在包括OnConnected的每个呼叫上接收自定义ID

时间:2016-06-01 20:17:41

标签: c# cookies asp.net-web-api signalr signalr-hub

我使用SignalR将消息从WebAPI服务器后端中继到JavaScript网页。这些消息只会转发给某些用户,因此我需要将SignalR ConnectionId映射到网页用户的自定义ID。

目前,WebAPI使用FormsAuthentication,我需要的自定义ID位于cookie中。

最初我继承了IUserIdProvider以从cookie中提取值:

public class CustomIdProvider : IUserIdProvider
{
    public string GetUserId(IRequest request)
    {
        Cookie formsAuthCookie = request.Cookies[FormsAuthentication.FormsCookieName];

        try
        {
            FormsAuthenticationTicket ticket = FormsAuthentication.Decrypt(formsAuthCookie.Value);

            var obj = JsonConvert.DeserializeObject(ticket.Name) as JObject;

            return (string)obj.GetValue("UserId");
        }
        catch (Exception)
        {
            return null;
        }
    }
}

正确地获取自定义ID。但据我所知,这个价值从未在身份上设定过。由于Context.User.Identity.Name全部是只读的,我也无法编辑任何标识值。

编辑:再次尝试CustomIdProvider,我正确地从cookie中获取值,但是从GetUserId方法返回时,永远不会调用OnConnected。

我的下一个方法是基于Shaun Xu的博客 Set Context User Principal For Customized Authentication In SignalR。 这是我的实施:

public class CustomAuthorizeAttribute : AuthorizeAttribute
{
    private const string CUSTOM_IDENTITY_KEY = "server.User";

    public override bool AuthorizeHubConnection(HubDescriptor hubDescriptor, IRequest request)
    {
        string customId;

        // This gets the custom id from the cookie.
        TryGetCustomId(request, out customId);

        // The CustomIdentity class just sets the customId to the name.     
        request.Environment.Add(CUSTOM_IDENTITY_KEY, new ClaimsPrincipal(new CustomIdentity(customId, true)));

        return true;
    }

    public override bool AuthorizeHubMethodInvocation(IHubIncomingInvokerContext hubIncomingInvokerContext, bool appliesToMethod)
    {
        string connectionId = hubIncomingInvokerContext.Hub.Context.ConnectionId;

        IDictionary<string, object> environment = hubIncomingInvokerContext.Hub.Context.Request.Environment;

        object obj;

        environment.TryGetValue(CUSTOM_IDENTITY_KEY, out obj);

        var principal = obj as ClaimsPrincipal;

        if (principal?.Identity == null || !principal.Identity.IsAuthenticated)
        {
            return false;
        }

        hubIncomingInvokerContext.Hub.Context = new HubCallerContext(new ServerRequest(environment), connectionId);

        return true;
    }

这实际上可以在调用两个方法时正确设置Context.User.Identity。

但是,我的问题是,当用户第一次连接并且OnConnected被调用时,它不会调用AuthorizeHubMethodInvocation,因此OnConnected中的Context.User.Identity不可用。

我希望能够在SignalR集线器的所有阶段访问包含我的自定义ID的正确标识:OnConnected,OnDisconnected和调用方法。

有没有人有这方面的好解决方案?

1 个答案:

答案 0 :(得分:1)

我有一些意见:

1)您不应尝试在授权阶段建立自定义标识。它应该是认证阶段的关注点。

2)实施自定义IUserIdProvider并建立Context.User.Identity的自定义身份ID 单独关注。自定义IUserIdProvider只是映射 Context.User.Identity的任何属性作为signalR中用户的标识符。

所以,解决你的问题。尝试在身份验证阶段建立自定义身份ID。根据您设置应用程序的方式,有很多方法可以执行此操作。例如:

1)在Application_PostAuthenticateRequest

中建立自定义身份ID
protected void Application_PostAuthenticateRequest(Object sender, EventArgs e)
{
     //Provide custom identity id to your HttpContext.Current.User
     //In your case, you may extract that information from your authentication ticket.
}

如果您需要详细信息,可以查看此帖子:ASP.NET MVC - Set custom IIdentity or IPrincipal

2)使用声明标识,您可以将自定义ID作为声明返回,每当浏览器向您的服务器发送请求时,您的声明标识都会重新建立。在下面的示例中,我使用owin cookie身份验证。

  var claims = new List<Claim>();
  claims.Add(new Claim(ClaimTypes.Name, user.Id.ToString()));

  var id = new ClaimsIdentity(claims, "Cookies");
  var ctx = Request.GetOwinContext();
  var authenticationManager = ctx.Authentication;
  authenticationManager.SignIn(id);

然后在IUserIdProvider实施中,您可以提取身份(Name属性,声明,...)中的相应信息,以用作signalR应用程序中的用户标识符。