最初開始接觸Azure IoT Hub的時候,被各種connection string和endpoint弄的眼花繚亂。本入門系列旨在將Azure IoT Hub 權限管理機制以及各個接口(endpoint)的用途解釋清楚。
首先拋出一個典型的IoT解決方案的架構,以讓讀者對IoT有個大概的認識。該架構通過平台層的核心服務和應用層組件來實現典型IoT解決方案需要解決的三個主要問題:
- 設備的連接;
- 數據的處理、分析與管理;
- 數據的有效呈現以及業務邏輯的處理。

回到本文的主題:IoT Hub 權限管理以及Endpoint。
- Azure IoT Hub 權限管理
總結起來,Azure 提供了以下兩種權限管理機制:
- Hub 層面的共享訪問策略(shared access policies)
在portal上新創建的IoT Hub默認包含了以下策略,你可以對已有的策略進行修改,或者添加新的策略。
- iothubowner: 擁有所有的權限
- service: ServiceConnect 權限 (給予服務端通信監控接口訪問權限,例如讀取device-to-cloud的消息,發送cloud-to-device消息等)
- device: DeviceConnect 權限(給予設備端的通信接口訪問權限,例如發送device-to-cloud消息)
- registryRead: RegistryRead 權限(讀設備注冊列表)
- registryReadWrite: RegistryRead和RegistryWrite權限(讀寫設備注冊列表)
- device 層面的安全令牌(security credentials)
IoT Hub維護了一個所有設備的注冊列表。列表里的每一個設備都有自己的symmetric key,用戶可以根據這個symmetric key 來獲得DeviceConnect的權限。
- Hub 層面的共享訪問策略(shared access policies)
-
針對特定場景下所需要的權限舉例如下:
-
* 設備管理組件:registryReadWrite 策略
-
* 事物處理組件:service 策略
-
* 單設備連接組件:device策略
- 如何生成安全令牌
為避免直接在網絡上傳輸密鑰,IoT Hub通過安全令牌來對設備以及雲端服務進行授權。一般情況下,Azure IoT Hub SDKs會自動根據密鑰生成安全令牌。但在某些情況下(例如直接使用MQTT,AMQP或者HTTP接口)需要客戶自己去生成安全令牌。
安全令牌的格式如下:
SharedAccessSignature sig={signature-string}&se={expiry}&skn={policyName}&sr={URL-encoded-resourceURI}針對每個字段的注解,請參考:https://azure.microsoft.com/en-us/documentation/articles/iot-hub-devguide-security/
下面給出完整的C#實現:
SharedAccessSignatureBuilderpublic class SharedAccessSignatureBuilder { private string key; public string Key { get { return this.key; } set { // StringValidationHelper.EnsureBase64String(value, "Key"); this.key = value; } } public string KeyName { get; set; } public string Target { get; set; } public TimeSpan TimeToLive { get; set; } public string TargetService { get; set; } public SharedAccessSignatureBuilder() { this.TimeToLive = TimeSpan.FromMinutes(20); TargetService = "iothub"; } private static string BuildExpiresOn(TimeSpan timeToLive) { DateTime dateTime = DateTime.UtcNow.Add(timeToLive); TimeSpan timeSpan = dateTime.Subtract(SharedAccessSignatureConstants.EpochTime); return Convert.ToString(Convert.ToInt64(timeSpan.TotalSeconds, CultureInfo.InvariantCulture), CultureInfo.InvariantCulture); } private static string BuildSignature(string keyName, string key, string target, TimeSpan timeToLive, string targetService = "iothub") { string str = SharedAccessSignatureBuilder.BuildExpiresOn(timeToLive); string str1 = WebUtility.UrlEncode(target); List<string> strs = new List<string>() { str1, str }; string str2 = SharedAccessSignatureBuilder.Sign(string.Join("\n", strs), key, targetService); StringBuilder stringBuilder = new StringBuilder(); stringBuilder.AppendFormat(CultureInfo.InvariantCulture, "{0} {1}={2}&{3}={4}&{5}={6}", new object[] { "SharedAccessSignature", "sr", str1, "sig", WebUtility.UrlEncode(str2), "se", WebUtility.UrlEncode(str) }); if (!string.IsNullOrEmpty(keyName)) { stringBuilder.AppendFormat(CultureInfo.InvariantCulture, "&{0}={1}", new object[] { "skn", WebUtility.UrlEncode(keyName) }); } return stringBuilder.ToString(); } private static string Sign(string requestString, string key, string targetService) { string base64String; if (!string.IsNullOrEmpty(targetService) && targetService.ToLower() == "servicebus") { using (HMACSHA256 hMACSHA256 = new HMACSHA256(Encoding.UTF8.GetBytes(key))) // key is not decoded { base64String = Convert.ToBase64String(hMACSHA256.ComputeHash(Encoding.UTF8.GetBytes(requestString))); } } else { using (HMACSHA256 hMACSHA256 = new HMACSHA256(Convert.FromBase64String(key))) // key is decoded { base64String = Convert.ToBase64String(hMACSHA256.ComputeHash(Encoding.UTF8.GetBytes(requestString))); } } return base64String; } public string ToSignature() { return SharedAccessSignatureBuilder.BuildSignature(this.KeyName, this.Key, this.Target, this.TimeToLive, this.TargetService); } }
SharedAccessSignatureConstantspublic class SharedAccessSignatureConstants { public const int MaxKeyNameLength = 256; public const int MaxKeyLength = 256; public const string SharedAccessSignature = "SharedAccessSignature"; public const string AudienceFieldName = "sr"; public const string SignatureFieldName = "sig"; public const string KeyNameFieldName = "skn"; public const string ExpiryFieldName = "se"; public const string SignedResourceFullFieldName = "SharedAccessSignature sr"; public const string KeyValueSeparator = "="; public const string PairSeparator = "&"; public readonly static DateTime EpochTime; public readonly static TimeSpan MaxClockSkew; static SharedAccessSignatureConstants() { SharedAccessSignatureConstants.EpochTime = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc); SharedAccessSignatureConstants.MaxClockSkew = TimeSpan.FromMinutes(5); } }
你也可以直接引用我編譯的DLL 文件,並在項目中引用它。
using SharedAccessSignatureGenerator;
調用方式:
1)根據IoT Hub 共享訪問策略生成安全令牌
假設IoT Hub 共享訪問策略連接串如下:HostName=<iot-hub-name>.azure-devices.cn;SharedAccessKeyName=<policy name>;SharedAccessKey=<key>
調用方法:
var sasBuilder = new SharedAccessSignatureBuilder() { KeyName = <policy name>, Key = <key>, Target = string.Format("{0}/devices", <iot-hub-name>), TimeToLive = TimeSpan.FromDays(Convert.ToDouble(ttlValue)) }; string sas = sasBuilder.ToSignature();
輸出示例:
SharedAccessSignature sr=devpod.azure-devices.cn%2Fdevices&sig=eAtQg7Du%2FUBrBk9zELLpOwELyGSVuOH0qHv1iJ63xnc%3D&se=1478756374&skn=iothubowner2)根據設備的symmetric key生成安全令牌
假設IoT Hub 設備的鏈接串如下:
HostName=<iot-hub-name>.azure-devices.cn;DeviceId=<deviceId>;SharedAccessKey=<key>
調用方法:
var sasBuilder = new SharedAccessSignatureBuilder() { Key = <key>, Target = string.Format("{0}/devices/{1}", <iot-hub-name>, WebUtility.UrlEncode(<deviceId>)), TimeToLive = TimeSpan.FromDays(Convert.ToDouble(ttlValue)) }; string sas = sasBuilder.ToSignature();
輸出示例:
SharedAccessSignature sr=devpod.azure-devices.cn%2Fdevices%2Fdevice001&sig=x6rceLUBASP99GU03LAX5w0YQ8gF05J6%2BYX5gJwKISQ%3D&se=1478756286
