你当前正在访问 Microsoft Azure Global Edition 技术文档网站。 如果需要访问由世纪互联运营的 Microsoft Azure 中国技术文档网站,请访问 https://docs.azure.cn

教程 - 使用事件处理程序基于客户端证书对 MQTT 客户端进行身份验证和授权

在本教程中,你将了解如何编写 .NET Web 服务器对 MQTT 客户端进行身份验证和授权。

先决条件

  • 具有活动订阅的 Azure 帐户。 如果你没有 Azure 帐户,可以免费创建一个帐户
  • Azure Web PubSub 服务(必须是标准层或更高层级)。
  • PEM 格式的客户端证书。
  • 已安装 .NET 运行时
  • Node.js

部署 Azure Web PubSub 服务

下面是 Bicep/Azure 资源管理器模板,用于部署启用了客户端证书身份验证并配置了事件处理程序的 Azure Web PubSub 服务。

我们配置 connect 事件处理程序的目的是告知服务用于对客户端进行身份验证和授权的 Webhook 终结点。 我们将其设置为 tunnel:///MqttConnecttunnel:// 是利用 awps-tunnel 工具向公共网络公开本地身份验证服务器的特殊语法。 /MqttConnect 是将由本地身份验证服务器公开的终结点。

我们通过属性 tls.clientCertEnabled 启用客户端证书身份验证,以便在 connect 事件中将客户端证书发送到服务器。

另请注意,anonymousConnectPolicy 需要设置为 allow,以便客户端不再需要发送访问令牌。

param name string
param hubName string = 'hub1'
param eventHandlerUrl string = 'tunnel:///MqttConnect'
param location string = resourceGroup().location

resource awps 'Microsoft.SignalRService/WebPubSub@2023-03-01-preview' = {
  name: name
  location: location
  sku: {
    name: 'Standard_S1'
    tier: 'Standard'
    size: 'S1'
    capacity: 1
  }
  properties: {
    tls: {
      clientCertEnabled: true
    }
  }
}

resource hub 'Microsoft.SignalRService/WebPubSub/hubs@2023-03-01-preview' = {
  parent: awps
  name: '${hubName}'
  properties: {
    eventHandlers: [
      {
        urlTemplate: eventHandlerUrl
        userEventPattern: '*'
        systemEvents: [
          'connect'
        ]
      }
    ]
    anonymousConnectPolicy: 'allow'
  }
}

设置身份验证服务器

我们在此处提供了身份验证服务器示例。 请下载该项目。

我们来看看项目结构:

- mqttAuthServer
  - Models
    - MqttConnectEventRequest.cs
    - ...
  - MqttAuthServer.csproj
  - Program.cs

Models 目录包含描述 MQTT connect 事件请求和响应正文的所有模型文件。 Program.cs 包含用于处理 MQTT connect 事件的逻辑,其中包括分析请求中的客户端证书内容、对证书进行验证和授权客户端。

以下代码片段是处理 connect 事件请求的主要逻辑:

    var request = await httpContext.Request.ReadFromJsonAsync<MqttConnectEventRequest>();
    var certificates = request.ClientCertificates.Select(cert => GetCertificateFromPemString(cert.Content));
    // Simulate Logic to validate client certificate
    if (!request.Query.TryGetValue("failure", out _))
    {
        // As a demo, we just accept all client certificates and grant the clients with permissions to publish and subscribe to all the topics when the query parameter "success" is present.
        await httpContext.Response.WriteAsJsonAsync(new MqttConnectEventSuccessResponse()
        {
            Roles = ["webpubsub.joinLeaveGroup", "webpubsub.sendToGroup"]
        });
    }
    else
    {
        // If you want to reject the connection, you can return a MqttConnectEventFailureResponse
        var mqttCodeForUnauthorized = request.Mqtt.ProtocolVersion switch
        {
            4 => 5, // UnAuthorized Return Code in Mqtt 3.1.1
            5 => 0x87, // UnAuthorized Reason Code in Mqtt 5.0
            _ => throw new NotSupportedException($"{request.Mqtt.ProtocolVersion} is not supported.")
        };
        httpContext.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
        await httpContext.Response.WriteAsJsonAsync(new MqttConnectEventFailureResponse(new MqttConnectEventFailureResponseProperties()
        {
            Code = mqttCodeForUnauthorized,
            Reason = "Invalid Certificate"
        }
        ));
    }

要运行项目,请在根目录中执行以下命令。

dotnet run

向公用网络公开服务器终结点

下载并安装 awps-tunnel

该工具在 Node.js 16 或更高版本上运行。

npm install -g @azure/web-pubsub-tunnel-tool

使用服务连接字符串并运行

export WebPubSubConnectionString="<your connection string>"
awps-tunnel run --hub {hubName} --upstream http://localhost:{portExposedByYourAuthServer}

实现 MQTT 客户端

我们将使用 Node.js 实现客户端。

使用以下命令初始化 Node.js 项目。

npm init

安装 mqtt 模块。

npm install mqtt

创建名为 index.js 的新文件,并将以下代码添加到该文件。

const mqtt = require('mqtt');

var client = mqtt.connect(`wss://{serviceName}.webpubsub.azure.com/clients/mqtt/hubs/{hubName}`,
    {
        clientId: "client1",
        cert: `-----BEGIN CERTIFICATE-----
{Complete the certificate here}
-----END CERTIFICATE-----`,
        key: `-----BEGIN PRIVATE KEY-----
{Complete the private key here}
-----END PRIVATE KEY-----`,
        protocolVersion: 5,
    });
client.on("connect", (connack) => {
    console.log("connack", connack);
});
client.on("error", (err) => {
    console.log(err);
});

更新 index.js

  • 根据创建的资源更新 {serviceName}{hubName} 变量。
  • 完成文件中的客户端证书和密钥。

然后,可以使用命令运行项目

node index.js

如果一切正常,你将能够在控制台中看到已打印的成功 CONNACK 响应。

connack Packet {
  cmd: 'connack',
  retain: false,
  qos: 0,
  dup: false,
  length: 2,
  topic: null,
  payload: null,
  sessionPresent: false,
  returnCode: 0
}

要模拟证书验证失败,请将失败查询追加到连接 URL,如下所示

var client = mqtt.connect(`wss://{serviceName}.webpubsub.azure.com/clients/mqtt/hubs/{hubName}?failure=xxx`,

然后重新运行客户端,你将能够看到未经授权的 CONNACK 响应。

下一步

现在,你已了解如何对 MQTT 客户端 e2e 进行身份验证和授权。 接下来,可以检查 MQTT 客户端的事件处理程序协议。