正如我在之前的几篇文章中所描述的那样,自我托管SignalR非常容易设置。这很容易做到,如果您需要将SignalR作为事件源连接到标准的基于Windows的应用程序(如服务,甚至是需要向许多用户发送推送通知的WPF或Winforms桌面应用程序),这很好

自托管的一个方面虽然不是那么透明或有记录,但是在SSL下运行自托管的SignalR服务。Windows证书存储以及证书的创建,配置和安装仍然很痛苦,因为Windows中没有提供链接端点到证书的UI,并且该流程的端到端记录不完整。一旦你知道需要调用什么命令行工具就很容易,但是这个过程当然可以更顺畅,更好地记录。因此,我在这里重新讨论这个主题,以提供更多详细信息,并希望能够更加一致地描述为自我托管OWIN服务(特别是SignalR)设置证书。

自托管和OWIN

当您自己托管SignalR时,您实际上使用的是OWIN / Katana提供的托管服务。OWIN是一个低级规范,用于实现可互换使用的自定义托管提供程序。我们的想法是将托管进程与特定实现分离并使其可插拔,因此您可以选择托管实现。

Katana是Microsoft的OWIN实现,它提供了几个特定的​​实现。对于自托管,基于HttpListener的主机与IIS及其基础架构完全分离。对于在ASP.NET内部托管,还有一个基于ASP.NET的实现,用于在ASP.NET内部运行的SignalR应用程序。这两种实现都为SignalR提供了基本托管支持,因此大多数情况下,相同的代码库可用于在ASP.NET下运行SignalR,或者在您自己的自托管EXE(如服务,控制台或桌面应用程序)下运行。

将证书绑定到SSL端口以进行自托管

HttpListener下的自托管非常精彩且完全独立,但不属于IIS的一个缺点是它也不知道为IIS安装的证书,这意味着您要使用的证书必须是显式绑定到端口。请注意,您可以使用IIS证书,如果需要获取完整证书以用于自托管应用程序,则通过IIS证书过程是获取证书的最简单方法。如果你需要一个本地测试证书,IIS的自签名证书创建工具也很容易(我将在下面描述)。

现在让我们假设您已经在Windows证书库中安装了证书。为了将证书绑定到自托管端点,您必须使用netsh命令行实用程序在计算机上注册它(全部在一行上):

netsh http add sslcert ipport=0.0.0.0:8082
           appid={12345678-db90-4b66-8b01-88f7af2e36bf} 
           certhash=d37b844594e5c23702ef4e6bd17719a079b9bdf

对于每个端点映射,您需要提供3个值:

  • 标识ip和端口的
    ipport指定为ipport = 0.0.0.0:8082,其中零表示端口8082上的所有IP地址。否则,您还可以指定特定的IP地址。

  • certhash是证书的指纹
    certhash是将证书映射到上面的IP端点的id。您可以通过查看Windows证书存储区中的证书来查找此哈希。更多关于这一点。

  • 为HttpListener Hosting修复的AppID
    此值是静态魔术值,因此始终使用appid={12345678-db90-4b66-8b01-88f7af2e36bf}一旦运行了上述命令,您应该通过查看绑定来检查它是否有效。用这个:

netsh http show sslcert ipport=0.0.0.0:8082

这给你一个这样的显示:

netsh的

查找CertHash

我在上面提到了certhash:要找到certhash,你需要找到证书的ThumbPrint,它可以通过以下几种方式找到:

  • IIS证书管理器
  • Windows证书存储管理器

使用IIS获取证书信息

如果安装了IIS,前者是最简单的。在这里,您可以轻松查看所有已安装的证书,此UI也是创建本地自签名证书的最简单方法。

要查找现有证书,只需打开IIS管理控制台,转到计算机节点,然后转到服务器证书:

IISCerts

您可以在最右侧的列中看到证书哈希。您也可以双击并打开证书,然后进入证书的详细信息。查找包含哈希的指纹。

CertificateDetails 

遗憾的是,这些位置都不容易复制哈希,因此您必须手动复制它或从对话框中的指纹数据中删除空格。

使用IIS创建自签名证书

如果您还没有完整的服务器证书,但是您希望在本地使用SSL操作进行测试,则还可以使用IIS Admin界面轻松创建自签名证书。IIS管理控制台提供了创建本地自签名证书的最简单方法之一。

这是怎么做的:

  • 转到IIS服务管理器的计算机根目录
  • 转到IIS部分中的服务器证书项
  • 在左侧单击“创建自签名证书”
  • 为其命名,然后选择个人存储
  • 单击确定

IisCreateCert

这就是创建自签名本地证书的全部内容。

将自签名证书复制到受信任的根证书存储区

拥有自签名证书后,还需要一个步骤才能使证书受信任,因此Http客户端将在您的计算机上接受它而不会出现证书错误。该过程涉及将证书从个人存储复制到受信任的机器商店。

去做这个:

  • 从StartMenu使用“ 管理计算机证书”
  • 进入个人| 证书并找到您的证书
  • 将证书拖放并复制(Ctrl-拖动)为“受信任的根证书” 证书

TrustCertificate

您现在应该拥有浏览器可信赖的证书。这适用于IE,Chrome和Safari,但FireFox需要一些特殊步骤(感谢Eric Lawrence),Opera还需要特定的证书注册。

使用完整的IIS证书

自签名证书非常适合在SSL下进行测试以确保您的应用程序正常工作,但对于生产应用程序而言并不是很好,因为证书必须安装在您希望信任此证书的任何计算机上,这是一件麻烦事。

一旦你去生产,特别是公共生产,你需要一个由$$$的全球证书颁发机构签署的“官方”证书(或者现在是LetsEncrypt)。

最简单的方法是购买或生成完整的IIS证书并将其安装在IIS中。IIS证书也可以用于使用HttpListener的自托管应用程序,因此它可以与自托管SignalR或任何HttpListener应用程序一起使用。

因此,一旦时间到了,请通过IIS注册新证书,然后使用netsh http add sslcert如上所示注册该证书。在大多数情况下,公共SSL证书已经被识别,因此不需要进一步移动证书存储 - 您只需要将netsh注册绑定到特定端口和应用程序ID。

使用SSL运行SignalR

安装证书后,将SignalR切换为SSL启动就像更改启动URL一样简单。

自托管服务器配置

在自托管服务器中,您现在可以在启动工厂调用中指定新的SSL URL:

var signalR = WebApp.Start<SignalRStartup>([https://*:8082/](https://*:8082/));

这会将SignalR绑定到端口8082上的所有IP地址。您还可以指定特定的IP地址,但使用*更具可移植性,尤其是将值设置为共享配置文件的一部分时。

如果你回忆起我上次的自托管帖子,OWIN使用一个启动类(在这种情况下是SignalRStartup)来处理OWIN和SignalR HubConfiguration,但唯一需要改变的是启动URL,你的自托管服务器已准备就绪走。

SignalR Web App页面URL配置

在Web页面上使用SignalR服务到集线器或连接更改脚本URL,为您的集线器或连接加载SignalR客户端库,如下所示:

<script src="[https://RasXps:8082/signalr/hubs">script>

这里的RasXps是我注册证书的确切本地机器名。与所有证书一样,请确保域名与证书的名称完全匹配。对于本地计算机,如果证书已默认分配给本地计算机NetBios名称,则表示不使用localhost不要使用您的IP地址 - 使用分配证书的任何内容。

您还需要将集线器Url分配给您的SSL URL,作为调用$ connection.hub.start的SignalR启动例程的一部分:

$.connection.hub.url = self.hubUrl; // ie. "[https://rasxps:8082/signalR](https://rasxps:8082/signalR);"

有关更多上下文,这是我用来启动集线器的典型集线器启动/错误处理程序设置例程:

startHub: function () {
    $.connection.hub.url = self.hubUrl;  // ie. "https://rasxps:8082/signalR";

    // capture the hub for easier access
    var hub  = $.connection.queueMonitorServiceHub;
                                    
    // This means the <script> proxy failed - have to reload
    if (hub == null) {
        self.viewModel.connectionStatus("Offline");                
        toastr.error("Couldn't connect to server. Please refresh the page.");
        return;
    }
            
    // Connection Events
    hub.connection.error(function (error) {                
        if (error)
            toastr.error("An error occurred: " + error.message);
        self.hub = null;
    });
    hub.connection.disconnected(function (error) {                
        self.viewModel.connectionStatus("Connection lost");
        toastr.error("Connection lost. " + error);                

        // IMPORTANT: continuously try re-starting connection
        setTimeout(function () {                    
            $.connection.hub.start();                    
        }, 2000);
    });            
            
    // map client callbacks
    hub.client.writeMessage = self.writeMessage;
    hub.client.writeQueueMessage = self.writeQueueMessage;            
    hub.client.statusMessage = self.statusMessage;
    …
              
    // start the hub and handle after start actions
    $.connection.hub
        .start()
        .done(function () {
            hub.connection.stateChanged(function (change) {
                if (change.newState === $.signalR.connectionState.reconnecting)
                    self.viewModel.connectionStatus("Connection lost");
                else if (change.newState === $.signalR.connectionState.connected) {
                    self.viewModel.connectionStatus("Online");

                    // IMPORTANT: On reconnection you have to reset the hub
                    self.hub = $.connection.queueMonitorServiceHub;
                }
                else if (change.newState === $.signalR.connectionState.disconnected)
                    self.viewModel.connectionStatus("Disconnected");
            })     
        .error(function (error) {
            if (!error)
                error = "Disconnected";
            toastr.error(error.message);
        })
        .disconnected(function (msg) {
            toastr.warning("Disconnected: " + msg);
        });
                                    
        self.viewModel.connectionStatus("Online");                

        // get initial status from the server (RPC style method)
        self.getServiceStatus();
        self.getInitialMessages();                    
        });            
},

从代码角度来看,除了两个小的URL代码更改之外,SSL操作没有任何变化,这很好。

而且......你已经完成了!

SSL配置

随着越来越多的应用程序需要传输安全性,SSL使用变得越来越重要。即使您的自托管SignalR应用程序没有明确要求SSL,如果SignalR客户端托管在运行SSL的网页内,您必须在SSL下运行SignalR,如果您希望它在没有浏览器错误消息或故障的情况下运行一些浏览器会拒绝SSL页面上的混合内容。

SSL配置总是拖累,因为它不直观,需要一些研究。如果HttpListener证书配置就像今天或更好的IIS配置一样简单,如果自托管应用程序可以使用已安装的IIS证书,那就太好了。不幸的是,它并不那么容易,你需要运行一个命令行实用程序,其中包含一些魔术ID。

安装证书并不是火箭科学,但它并没有完全记录在案。在寻找信息的过程中,我发现了一些不相关的文章,讨论了这个过程,但有些文章已经过时,其他文章没有具体涉及SignalR甚至是自托管网站。所以我希望这篇文章能够在适当的环境中更容易地找到这些信息。

本文重点介绍使用SSL的SignalR自托管,但相同的概念可以应用于使用HttpListener的任何自托管应用程序。

资源