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

了解 Azure IoT 中心设备预配服务的自定义分配策略

自定义分配策略让你能够更好地控制将设备分配到 IoT 中心的方式。 当设备预配服务 (DPS) 提供的内置策略不能满足你的方案要求时,你可以使用自定义分配策略来定义自己的分配策略。

例如,你可能想要检查设备在预配过程中所使用的证书,并根据证书属性将该设备分配到 IoT 中心。 或者,你可能在数据库中存储了设备的信息,并需要查询数据库,以确定应将该设备分配到哪个 IoT 中心或如何设置设备的初始孪生。

Azure Functions 托管的 Webhook 中实现自定义分配策略。 然后,可以在一个或多个单独注册和注册组中配置 Webhook。 当设备通过配置的注册条目注册时,DPS 会调用 Webhook,该 Webhook 返回要将设备注册到的 IoT 中心、设备的初始孪生设置(可选)以及要直接返回给设备的任何信息。

概述

以下步骤说明了自定义分配策略的工作原理:

  1. 自定义分配开发人员开发一个 Webhook,该 Webhook 实现预期的分配策略并将其作为 HTTP 触发器函数部署到 Azure Functions。 Webhook 提取有关 DPS 注册条目和设备的信息,并返回设备应注册到的 IoT 中心,以及有关设备初始状态的信息(可选)。

  2. IoT 操作员为自定义分配配置一个或多个单独注册和/或注册组,并为 Azure Functions 中的自定义分配 Webhook 提供调用详细信息。

  3. 当设备通过为自定义分配 Webhook 配置的注册条目注册时,DPS 会向 Webhook 发送一个 POST 请求,其请求正文设置为 AllocationRequest 请求对象。 AllocationRequest 对象包含有关尝试预配的设备以及该设备在预配时所用的单独注册或注册组的信息。 设备信息可能包括设备在其注册请求中发送的可选自定义有效负载。 有关详细信息,请参阅自定义分配策略请求

  4. Azure 函数将会执行,并在成功时返回 AllocationResponse 对象。 AllocationResponse 对象包含设备应预配到的 IoT 中心、初始孪生状态,以及返回给设备的可选自定义有效负载。 有关详细信息,请参阅自定义分配策略响应

  5. DPS 将设备分配到响应中指明的 IoT 中心,如果返回了初始孪生,则相应地设置设备的初始孪生。 如果 Webhook 返回了自定义有效负载,则会将该有效负载连同 DPS 的注册响应中提供的已分配 IoT 中心和身份验证详细信息一起传递给设备。

  6. 设备连接到已分配的 IoT 中心并下载其初始孪生状态。 如果在注册响应中返回的自定义有效负载为 00000000000000000000000000000000000000000000000000000000,设备将根据自身的客户端逻辑使用该有效负载。

以下部分提供有关自定义分配请求和响应、自定义有效负载和策略实现的更多详细信息。 有关自定义分配策略的完整端到端示例,请参阅使用自定义分配策略

管理函数密钥

自定义分配策略使用函数密钥对 Azure Functions 调用进行身份验证,其中授权级别设置为 Function。 密钥管理行为因你是通过 Azure 门户还是以编程方式配置自定义分配策略而异。

门户中的函数密钥

在 Azure 门户中创建注册并指定自定义分配策略时,门户会自动处理检索并嵌入函数密钥。

为自定义分配策略选择函数后,门户将检索函数密钥。 用户无法通过门户界面看到此步骤。 然后,函数密钥作为 DPS 用来调用函数的加密 Webhook URL 的一部分存储。 该密钥并不显示在门户中。

可以通过运行 GET 命令来检索注册详细信息,以验证密钥是否嵌入 Webhook URL 中。 在注册配置中,函数密钥包含在 webhookUrl 字段中

使用 API 的函数密钥

使用 DPS API 以编程方式创建注册时,需要在创建注册期间手动提供密钥。 如果未提供密钥,Azure Functions 调用将无法通过身份验证。

在创建个人或组注册之前,请从函数中检索函数密钥。 有关详细信息,请参阅获取函数访问密钥。 然后,在 CustomAllocationDefinition 的 webhookUrl 字段中包含函数密钥

有关详细信息,请参阅 Azure Functions HTTP 触发器与访问密钥授权

自定义分配策略请求

DPS 在以下终结点向 Webhook 发送 POST 请求:https://{your-function-app-name}.azurewebsites.net/api/{your-http-trigger}

请求正文是一个 AllocationRequest 对象:

属性名称 说明
individualEnrollment 一条单独注册记录,其中包含与分配请求源自的单独注册关联的属性。 如果设备正在通过单独注册进行注册,则会提供该记录。
enrollmentGroup 一条注册组记录,其中包含与分配请求源自的注册组关联的属性。 如果设备正在通过注册组进行注册,则会提供该记录。
deviceRuntimeContext 包含与正在注册的设备关联的属性的对象。 始终显示。
linkedHubs 一个包含 IoT 中心主机名的数组,这些 IoT 中心已链接到分配请求源自的注册条目。 可将设备分配到其中的任何一个 IoT 中心。 始终显示。

DeviceRuntimeContext 对象具有以下属性:

属性 类型​​ 说明
registrationId string 设备在运行时提供的注册 ID。 始终显示。
currentIotHubHostName string 设备先前分配到的 IoT 中心的主机名(如果有)。 如果这是初始分配,则不提供。 可以使用此属性来确定这是设备的初始分配,还是先前已分配的设备。
currentDeviceId string 设备的先前分配中的设备 ID(如果有)。 如果这是初始分配,则不提供。
x509 X509DeviceAttestation 对于 X.509 证明,其中包含证书详细信息。
symmetricKey SymmetricKeyAttestation 对于对称密钥证明,其中包含主密钥和辅助密钥详细信息。
tpm TpmAttestation 对于 TPM 证明,其中包含认可密钥和存储根密钥详细信息。
payload object 包含注册期间设备在有效负载属性中指定的属性。 如果设备在 DPS 注册请求中发送了自定义有效负载,则提供。

以下 JSON 显示了 DPS 为通过基于对称密钥的注册组注册的设备发送的 AllocationRequest 对象。

{
   "enrollmentGroup":{
      "enrollmentGroupId":"contoso-custom-allocated-devices",
      "attestation":{
         "type":"symmetricKey"
      },
      "capabilities":{
         "iotEdge":false
      },
      "etag":"\"13003fea-0000-0300-0000-62d1d5e50000\"",
      "provisioningStatus":"enabled",
      "reprovisionPolicy":{
         "updateHubAssignment":true,
         "migrateDeviceData":true
      },
      "createdDateTimeUtc":"2022-07-05T21:27:16.8123235Z",
      "lastUpdatedDateTimeUtc":"2022-07-15T21:02:29.5922255Z",
      "allocationPolicy":"custom",
      "iotHubs":[
         "custom-allocation-toasters-hub.azure-devices.net",
         "custom-allocation-heatpumps-hub.azure-devices.net"
      ],
      "customAllocationDefinition":{
         "webhookUrl":"https://custom-allocation-function-app-3.azurewebsites.net/api/HttpTrigger1?****",
         "apiVersion":"2021-10-01"
      }
   },
   "deviceRuntimeContext":{
      "registrationId":"breakroom499-contoso-tstrsd-007",
      "symmetricKey":{
         
      }
   },
   "linkedHubs":[
      "custom-allocation-toasters-hub.azure-devices.net",
      "custom-allocation-heatpumps-hub.azure-devices.net"
   ]
}

由于这是设备的初始注册,因此 deviceRuntimeContext 属性仅包含设备的注册 ID 和身份验证详细信息。 以下 JSON 显示了用于注册同一设备的后续调用的 deviceRuntimeContext。 请注意,请求中包含当前 IoT 中心主机名和设备 ID。

{
   "deviceRuntimeContext":{
      "registrationId":"breakroom499-contoso-tstrsd-007",
      "currentIotHubHostName":"custom-allocation-toasters-hub.azure-devices.net",
      "currentDeviceId":"breakroom499-contoso-tstrsd-007",
      "symmetricKey":{
         
      }
   },
}

自定义分配策略响应

成功的请求会返回 AllocationResponse 对象。

属性 说明
initialTwin 可选。 一个对象,其中包含要在分配的 IoT 中心上的初始孪生中设置的所需属性和标记。 如果注册条目的迁移策略设置为“重新预配并重置为初始配置”,则 DPS 将使用 initialTwin 属性在初始分配中或者在重新预配时在分配的 IoT 中心上设置初始孪生。在这两种情况下,如果 initialTwin 未返回或设置为 null,则 DPS 会将分配的 IoT 中心上的孪生设置为注册条目中的初始孪生设置。 DPS 会忽略注册条目中所有其他重新预配设置的 initialTwin。 有关详细信息,请参阅实现详细信息
iotHubHostName 必需。 要将设备分配到的 IoT 中心的主机名。 此中心必须是在请求中的 linkedHubs 属性内传递的 IoT 中心之一。
payload 可选。 一个对象,其中包含要在注册响应中传回设备的数据。 确切的数据取决于开发人员在设备与自定义分配函数之间定义的隐式协定。

以下 JSON 显示了自定义分配函数根据上述示例注册返回给 DPS 的 AllocationResponse 对象。

{
   "iotHubHostName":"custom-allocation-toasters-hub.azure-devices.net",
   "initialTwin":{
      "properties":{
         "desired":{
            "state":"ready",
            "darknessSetting":"medium"
         }
      },
      "tags":{
         "deviceType":"toaster"
      }
   }
}

在自定义分配中使用设备有效负载

设备可将 DPS 传递的自定义有效负载发送到自定义分配 Webhook,后者随后可以在其逻辑中使用该有效负载。 Webhook 可以通过多种方式使用这些数据,使用目的也许是为了确定要将设备分配到哪个 IoT 中心,或者在外部数据库中查找可用于设置初始孪生上的属性的信息。 相反,Webhook 可以通过 DPS 将数据返回给设备,这些数据可在设备的客户端逻辑中使用。

例如,你可能希望根据设备型号分配设备。 在这种情况下,可将设备配置为在向 DPS 注册时在请求有效负载中报告其型号信息。 DPS 会将此有效负载传递给自定义分配 Webhook,后者将根据设备型号信息确定要将设备预配到哪个 IoT 中心。 如果需要,Webhook 可以在 Webhook 响应中将数据作为 JSON 对象返回给 DPS,而 DPS 在注册响应中将此数据返回给设备。

设备将数据有效负载发送到 DPS

设备调用注册 API 来向 DPS 注册。 可以使用可选的 payload 属性来增强请求。 此属性可以包含任何有效 JSON 对象。 确切的内容取决于解决方案的要求。

对于 TPM 证明,请求正文如下所示:

{ 
    "registrationId": "mydevice", 
    "tpm": { 
        "endorsementKey": "xxxx-device-endorsement-key-xxxxx", 
        "storageRootKey": "xxxx-device-storage-root-key-xxxxx" 
    }, 
    "payload": { "property1": "value1", "property2": {"propertyA":"valueA", "property2-2":1234}, .. } 
} 

DPS 将数据有效负载发送到自定义分配 Webhook

如果设备在其注册请求中包含有效负载,则 DPS 在调用自定义分配 Webhook 时会在 AllocationRequest.deviceRuntimeContext.payload 属性中传递该有效负载。

对于上一部分中的 TPM 注册请求,设备运行时上下文如下所示:

{ 
    "registrationId": "mydevice", 
    "tpm": { 
        "endorsementKey": "xxxx-device-endorsement-key-xxxxx", 
        "storageRootKey": "xxxx-device-storage-root-key-xxxxx" 
    }, 
    "payload": { "property1": "value1", "property2": {"propertyA":"valueA", "property2-2":1234}, .. } 
} 

如果这不是设备的初始注册,则运行时上下文还包含 currentIoTHubHostname 和 currentDeviceId 属性。

自定义分配 Webhook 将数据返回给 DPS

自定义分配策略 Webhook 可以使用 Webhook 响应中的 AllocationResponse.payload 属性,在 JSON 对象中将预期发往设备的数据返回给 DPS。

以下 JSON 显示了包含有效负载的 Webhook 响应:

{
   "iotHubHostName":"custom-allocation-toasters-hub.azure-devices.net",
   "initialTwin":{
      "properties":{
         "desired":{
            "state":"ready",
            "darknessSetting":"medium"
         }
      },
      "tags":{
         "deviceType":"toaster"
      }
   },
   "payload": { "property1": "value1" } 
}

DPS 将数据有效负载发送到设备

如果 DPS 在 Webhook 响应中收到有效负载,则在成功注册后,它会在响应中的 RegistrationOperationStatus.registrationState.payload 属性内将此数据传回给设备。 registrationState 属性的类型为 DeviceRegistrationResult

以下 JSON 显示了 TPM 设备的成功注册响应,其中包含 payload 属性:

{
   "operationId":"5.316aac5bdc130deb.b1e02da8-xxxx-xxxx-xxxx-7ea7a6b7f550",
   "status":"assigned",
   "registrationState":{
      "assignedHub":"myIotHub",
      "createdDateTimeUtc" : "2022-08-01T22:57:47Z",
      "deviceId" : "myDeviceId",
      "etag" : "xxxx-etag-value-xxxxx",
      "lastUpdatedDateTimeUtc" : "2022-08-01T22:57:47Z",
      "payload": { "property1": "value1" },
      "registrationId": "mydevice", 
      "status": assigned,
      "substatus": initialAssignment,
      "tpm": {"authenticationKey": "xxxx-encrypted-authentication-key-xxxxx"}
   }
}

实现详细信息

可以针对先前未通过 DPS 注册的设备(初始分配)或先前已通过 DPS 注册的设备(重新预配)调用自定义分配 Webhook。 DPS 支持以下重新预配策略:“重新预配并迁移数据”、“重新预配并重置为初始配置”和“永不重新预配”。 只要将先前预配的设备分配到新的 IoT 中心,就会应用这些策略。 有关更多详细信息,请参阅重新预配

以下要点描述了自定义分配 Webhook 必须遵守的要求,以及在设计 Webhook 时应注意的行为:

  • 应将设备分配到 AllocationRequest.linkedHubs 属性中的 IoT 中心之一。 此属性包含设备可分配到的 IoT 中心的列表,该列表按主机名列出。 此列表通常由为注册条目选择的 IoT 中心组成。 如果未在注册条目中选择任何 IoT 中心,此列表将包含链接到 DPS 实例的所有 IoT 中心。 最后,如果设备正在重新预配并且在注册条目上设置了“永不重新预配”策略,则此列表仅包含设备当前分配到的 IoT 中心。

  • 在初始分配时,如果 Webhook 返回了 initialTwin 属性,则 DPS 会相应地为分配的 IoT 中心上的设备设置初始孪生。 如果 initialTwin 属性被省略或为 null,则 DPS 会将设备的初始孪生设置为注册条目中指定的初始孪生设置。

  • 重新预配时,DPS 遵循注册条目中设置的重新预配策略。 仅当当前 IoT 中心发生更改并且在注册条目上设置的重新预配策略是“重新预配并重置为初始配置”时,DPS 才在响应中使用 initialTwin 属性。在这种情况下,DPS 将按照上一要点中在初始分配期间采用的完全相同方式,为新 IoT 中心上的设备设置初始孪生。 在所有其他情况下,DPS 都会忽略 initialTwin 属性。

  • 如果在响应中设置了 payload 属性,则无论请求是用于初始分配还是重新预配,DPS 都始终会将该属性返回给设备。

  • 如果设备先前已预配到 IoT 中心,则 AllocationRequest.deviceRuntimeContext 将包含一个 currentIotHubHostName 属性,该属性设置为设备当前分配到的 IoT 中心的主机名。

  • 可以通过检查请求中 AllocationRequest.individualEnrollment 或 AllocationRequest.enrollmentGroup 属性的 reprovisionPolicy 属性,确定当前在注册条目上设置了哪个重新预配策略。 以下 JSON 显示了“重新预配并迁移数据”策略的设置:

           "reprovisionPolicy":{
              "updateHubAssignment":true,
              "migrateDeviceData":true
           }
    

SDK 支持

DPS 设备 SDK 在 C、C#、Java 和 Node.js 中提供了 API,以帮助你将设备注册到 DPS。 IoT 中心 SDK 和 DPS SDK 都提供了代表设备和服务项目(例如设备孪生和注册条目)的类,在开发自定义分配 Webhook 时,这些类可能有所帮助。 若要详细了解可用于 IoT 中心和 IoT 中心设备预配服务的 Azure IoT SDK,请参阅 Azure IoT SDKAzure DPS SDK

后续步骤