Assign/Push SignalR 上下文的 ClaimsPrincipal

Assign/Push ClaimsPrincipal to SignalR Context

好吧,所以这肯定是把我推向了悬崖的边缘......在相对轻松地使用 SignalR 的 V1 后,我现在肯定在挣扎。

问题: "Grabbing" Claims 来自最近通过身份验证的用户和 passing/using 在 Hub

环境:

WebAPI 在 SSL 上使用 Basic Auth 进行身份验证。验证逻辑在一个属性中并且工作正常并且已经持续了几个月,它还在 AuthenticateAsync

中声明 generates/builds

在我的 API Controller 中,我抓住了声明:

var idenityInstance = new IdentityInstance(this.User as ClaimsPrincipal);

我整个周末都在尝试利用 Groups/SSL 和身份验证合并 SignalR 的 V2。

我通常认为 ClaimsPrincipal 会被忽略或在相同的 "Context" 中被忽略。

            public void Configuration(IAppBuilder app)
            {
                // Branch the pipeline here for requests that start with "/signalr"
                app.Map("/signalr", map =>
                {
                    // Setup the CORS middleware to run before SignalR.
                    // By default this will allow all origins. You can 
                    // configure the set of origins and/or http verbs by
                    // providing a cors options with a different policy.
                    map.UseCors(CorsOptions.AllowAll);
                    var hubConfiguration = new HubConfiguration
                    {
                        // You can enable JSONP by uncommenting line below.
                        // JSONP requests are insecure but some older browsers (and some
                        // versions of IE) require JSONP to work cross domain
                        // EnableJSONP = true
                    };
                    // Run the SignalR pipeline. We're not using MapSignalR
                    // since this branch already runs under the "/signalr"
                    // path.
                    map.RunSignalR(hubConfiguration);
                });

            }

自定义授权属性(从文档中获取)

        protected override bool UserAuthorized(System.Security.Principal.IPrincipal user)
        {
            if (user == null)
            {
                throw new ArgumentNullException("user");
            }

            var principal = user as ClaimsPrincipal;

            if (principal != null)
            {
                Claim authenticated = principal.FindFirst(ClaimTypes.Authentication);
                if (authenticated != null && authenticated.Value == "true")
                {
                    return true;
                }
                else
                {
                    return false;
                }
            }
            else
            {
                return false;
            }
        }

好的,最后一个片段,The Hub(剥离)

        public async Task Hello()
        {
            //Temp line for testing under local SSL/Self signed
            ServicePointManager.ServerCertificateValidationCallback
                += (sender, cert, chain, sslPolicyErrors) => true;

            var hubConnection = new HubConnection("https://localhost:44357");


            IHubProxy chatHubProxy = hubConnection.CreateHubProxy("printerHub");



            hubConnection.Start().Wait();
            var c = Context;
            await hubContext.Clients.All.hello("Hello");
        }

同样的问题是,当我通过 Hub Method Hello(); 进行调试时,Context 始终是 null,最终不会 authenticate,因为自定义 attribute

我希望我刚刚错过了一些愚蠢的东西,因为它确实非常令人沮丧。

好的,这比最初预期的要棘手一些,现在解决方案可能不适合 "some" 环境,但在这种情况下,解决了问题。

始终牢记我们正在处理两个 Contexts...

重述一下,我有一个 WebAPIbasic auth 超过 SSL - 它工作得非常好,现在的想法是提供一种方法来锁定 Hubs 并且只允许访问 已验证 用户和 basic auth 范围之外的用户。由于 WebAPISignalR 运行 中两个进程对 Hubs 的访问可以在 WebAPI 管道之外。

一点代码来演示:

枢纽:

    [HubAuthorizationAttribute]
    [HubName("printerHub")]
    public class PrinterHub : Hub
    {
        private static IHubContext hubContext =
            GlobalHost.ConnectionManager.GetHubContext<PrinterHub>();


        private IdentityInstance _userInstanceContext;
        private User _user;
        public override Task OnConnected()
        {
            //Custom logic to return a User object
            _userInstanceContext = new IdentityInstance(this.Context.User as ClaimsPrincipal);
            _user = _userInstanceContext.Create();
            return base.OnConnected();
        }
        public override Task OnDisconnected(bool stopping)
        {
            return base.OnDisconnected(stopping);
        }
        public async Task JoinPrinterNotifications()
        {
            await hubContext.Groups.Add(Context.ConnectionId, _user.ClientName + ":Printers");
        }
        public async Task PrintQueues(IEnumerable<QueuedPrintItem> items)
        {
            await hubContext.Clients.Group(_user.ClientName + ":Printers").notify(items);
        }

SignalR 自定义身份验证属性

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

    //This should be sent in the Header - but will not work with
    //Websockets, if a custom header is required, ensure the transport type is "LongPolling" for demo purposes, QueryString was chosen. 
    var token = request.QueryString.Get("Token");
    if (string.IsNullOrWhiteSpace(token))
        return false;

        //Logic omitted to perform checks and the validity of a request

        if (client == null)
            return false;

        //build the claims identity
        Claim clientIdClaim = new Claim("Id", "SignalR: " + client.Id.ToString());
        Claim clientNameClaim = new Claim(ClaimTypes.Name, "SignalR: " + client.ClientName);
        List<Claim> claims = new List<Claim>
        {
            clientIdClaim,
            clientNameClaim,
        };
        //"Basic" is required to ensure IsAuthenticated is set, amongst others.
        ClaimsIdentity identity = new ClaimsIdentity(claims, "Basic");
        // set the authenticated user principal into environment so that it can be used in the future
        request.Environment["server.User"] = new ClaimsPrincipal(identity);
        return true;
  }
  public override bool AuthorizeHubMethodInvocation(IHubIncomingInvokerContext hubIncomingInvokerContext, bool appliesToMethod)
  {

    var connectionId = hubIncomingInvokerContext.Hub.Context.ConnectionId;
    // check the authenticated user principal from environment
    var environment = hubIncomingInvokerContext.Hub.Context.Request.Environment;
    var principal = environment["server.User"] as ClaimsPrincipal;
    if (principal == null || 
        principal.Identity == null || 
        principal.Identity.IsAuthenticated == false)
        return false;

        // create a new HubCallerContext instance with the principal generated from token
        // and replace the current context so that in hubs we can retrieve current user identity
        hubIncomingInvokerContext.Hub.Context = new HubCallerContext(new ServerRequest(environment), connectionId);
        return true;
}