第三方令牌伺服器的 Azure IoT 用戶端 SDK 支援

Azure IoT
Azure IoT 中樞

控制 IoT 中樞 存取一文說明第三方令牌服務如何與 IoT 中樞 整合。 本文概述每個 Azure IoT 用戶端 SDK 中共用存取簽章 (SAS) 令牌驗證的支援。 它也會概述使用每個語言的對應 SDK 在裝置應用程式中實作的專案,以及如何針對裝置 連線 或模組 連線 的共用存取原則使用裝置範圍或模組範圍的令牌。

內容和問題

目前 Azure IoT 中樞 安全性文件討論使用 Azure IoT 用戶端 SDK 向 IoT 裝置 IoT 中樞 進行 SAS 驗證的第三方令牌伺服器模式。 不過,在最近的企業參與期間,客戶所做的不正確假設表明,如果不進一步釐清,您可以針對 Azure IoT 用戶端 SDK 中預設實作的支援層級產生誤導性印象。

本文討論該參與的學習,並釐清每個 SDK 中需要針對裝置完成哪些動作,以達成第三方令牌伺服器驗證。 本文也應該防止對 Azure IoT 用戶端 SDK 中第三方令牌伺服器模式的支援做出類似的不正確假設。

解決方案

Azure IoT 用戶端 SDK 提供不同層級的 SAS 令牌驗證支援,每個 SDK 都需要一些自定義程式碼才能完成驗證和令牌管理功能。

令牌評估頻率取決於所選的傳輸通訊協定—MQTT、AMQP 或 HTTPS。 變化取決於通訊協定的功能,以支援令牌和會話逾時主動更新。 只有AMQP會實作主動式更新支援。 這表示其他傳輸會在 SAS 令牌驗證失敗時關閉連線,然後需要執行新的連線作業。 這是用戶端可能耗費資源的連線作業。

如果 SAS 驗證失敗,傳輸實作會引發錯誤,此實作可由「連線 ion Status Changed」事件處理程式在裝置應用程式中處理。 無法實作這類處理程式通常會因為錯誤而讓裝置應用程式停止。 透過事件處理程式和令牌更新功能的正確實作,傳輸可以重新嘗試連線。

下圖說明第三方令牌伺服器模式:

第三方 Token-server 模式圖例

下圖說明 Azure IoT 用戶端 SDK 與 Mobile Net Operator 整合的實作支援:

Azure IoT 用戶端 SDK 與 Mobile Net Operator 整合的實作支援流程圖

範例實作包含在 GitHub 上的 Azure 範例 存放庫中。

問題和考量

決定是否要實作此模式時,請考慮下列幾點:

使用此模式的時機

每當您想要使用各種 Azure IoT 用戶端 SDK 驗證以從 IoT 裝置 Azure IoT 中樞 時,都應該使用此模式。 使用 Azure DPS REST API,而不是使用用戶端 SDK 進行 SAS 令牌驗證,以確保對所有傳輸機制的主動式更新支持實作。

範例

下列各節提供可用於不同程序設計語言的範例,例如 Embedded C、.NET、Java 和 Python。

Azure IoT 中樞 適用於 C 的裝置 SDK 和適用於內嵌 C 的 Azure IoT 中樞 裝置 SDK

下列方法可用於使用 Azure IoT C SDK 或 Azure IoT Embedded C SDK 所建置的裝置應用程式中。 這兩個 SDK 都未提供 SAS 令牌存留期管理,因此您必須實作 SAS 令牌存留期管理員功能。

SAS 令牌可透過IOTHUB_CLIENT_CONFIG結構使用,方法是將 deviceSasToken 成員設定為令牌,並將 deviceKey 設為 null。 其他未使用的值,例如 protocolGatewayHostName,也必須設定為 null。

IOTHUB_CLIENT_CONFIG* CONFIG = (IOTHUB_CLIENT_CONFIG*)malloc(sizeof(IOTHUB_CLIENT_CONFIG));

CONFIG->PROTOCOL = PROTOCOL;
CONFIG->DEVICEID = DEVICEID;
CONFIG->IOTHUBNAME = IOTHUBNAME;
CONFIG->IOTHUBSUFFIX = IOTHUBSUFFIX;
CONFIG->DEVICEKEY = 0;
CONFIG->DEVICESASTOKEN = TOKEN;
CONFIG->PROTOCOLGATEWAYHOSTNAME = 0;

// The created IOTHUB_CLIENT_CONFIG can then be provided to the IoTHubDeviceClient_Create function to establish a DeviceClient instance.
if ((IOTHUBCLIENTHANDLE = IoTHubDeviceClient_Create(CONFIG)) == NULL) {
    (void)printf("ERROR: IOTHUBCLIENTHANDLE IS NULL!\r\n");
}

// To capture SAS token authentication failures, a handler needs to be implemented for the IoTHubDeviceClient_SetConnectionStatusCallback.
(void)IoTHubDeviceClient_SetConnectionStatusCallback(IOTHUBCLIENTHANDLE, CONNECTION_STATUS_CALLBACK, NULL);

connection_status_callback可以攔截IOTHUB_CLIENT_CONNECTION_EXPIRED_SAS_TOKEN的IOTHUB_CLIENT_CONNECTION_STATUS_REASON,以透過第三方令牌服務觸發 SAS 令牌的更新。 這是所有傳輸擷取連線問題的必要專案,但傳輸特別需要,這些傳輸不支援主動式 SAS 令牌更新。 主動式 SAS 令牌存留期管理可以實作為在裝置應用程式「操作」循環期間重複執行的函式。 確保經常評估令牌的存留期,並在需要時主動執行令牌更新。

C SDK 的 SAS 令牌驗證實作摘要:

  1. 實作 連線 ionStatusCallback 處理程式,以擷取IOTHUB_CLIENT_CONNECTION_EXPIRED_SAS_TOKEN事件和觸發令牌更新。

  2. 使用IOTHUB_CLIENT_CONFIG提供裝置 SAS 令牌給IoTHubDeviceClient_Create。

  3. 在裝置應用程式的作業迴圈中實作主動式 SAS 令牌存留期管理。

Azure IoT 中樞 適用於 .NET 的裝置 SDK

適用於 .NET 的 Azure IoT 用戶端 SDK 會透過抽象的 DeviceAuthenticationWithTokenRefresh 類別,實作 SAS 令牌存留期管理的支援。 這個類別的具體實作可新增令牌更新功能,以驗證方法的形式提供給 DeviceClient.Create 方法。 傳輸實作會視需要透過驗證方法自動更新令牌。 需要 連線 ionStatusChangesHandler 才能擷取連線變更,並防止傳輸引發例外狀況。

以 DeviceAuthenticationWithTokenRefreash 類別為基礎的範例實作:

internal class StsDeviceAuthenticationWithTokenRefresh : DeviceAuthenticationWithTokenRefresh
{

    private readonly string _stsConnectUrl = "http://localhost:8080/sts/azure/token/operations?sr={0}/devices/{1}";

    private const int DEFAULTTIMETOLIVESECONDS = 1 * 60 * 60;

    private const int DEFAULTBUFFERPERCENTAGE = 15;

    public StsDeviceAuthenticationWithTokenRefresh(string deviceId, int suggestedTimeToLiveSeconds, int timeBufferPercentage) : BASE(deviceId, suggestedTimeToLiveSeconds, timeBufferPercentage)
    {
        If(String.IsNullOrWhitespace(deviceId)){
            throw new ArgumentNullException(nameof(deviceId));
        }
    }

    protected override async Task<string> SafeCreateNewToken(string iotHub, int suggestedTimeToLive)
    {
        string result;
        string url = string.Format(_stsConnectUrl, iotHub, deviceId);

        using (HttpClientHandler handler = new HttpClientHandler())
        using (HttpClient client = new HttpClient(handler))
        {
            try
            {
                HttpResponseMessage response = await client.GetAsync(url);
                if (response.IsSuccessStatusCode)
                {
                    result = await response.Content.ReadAsStringAsync();
                }
                else
                {
                    throw new HttpRequestException($"Request failed with status code {response.StatusCode}.");
                }
            }
            catch (HttpRequestException)
            {
                result = null;
            }
        }

        return result;
    }
}

適用於 .NET 的 Azure IoT 中樞 裝置 SDK 的 SAS 令牌驗證實作摘要:

  1. 根據實作令牌更新功能的 DeviceAuthenticationWithTokenRefresh 抽象類實作具體類別。

  2. 實作 連線 ionStatusChangesHandler 以擷取傳輸連線狀態,並避免傳輸實作引發的例外狀況。

參考:

Azure IoT 中樞 適用於 Java 的裝置 SDK

適用於 Java 的 Azure IoT 用戶端 SDK 會透過 SasTokenProvider 介面實作 SAS 令牌存留期管理的支援。 使用 SAS 令牌更新功能實作此介面的類別,可作為 DeviceClient 建構函式中的 SecurityProvider。 傳輸實作會視需要透過安全性提供者自動更新令牌。 必須註冊 連線 ionStatusChangeCallback 來擷取連線變更,並防止傳輸引發例外狀況。

實作 SasTokenProvider 介面之安全性提供者的範例實作:

import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;

public class StsSecurityProvider implements SasTokenProvider {
    private final String hostname;
    private final String deviceId;
    private int renewalBufferSeconds;
    private long expiryTimeSeconds;
    private char[] sasToken;

    public StsSecurityProvider(String hostname, String deviceId) {
        this.hostname = hostname;
        this.deviceId = deviceId;
        this.renewalBufferSeconds = 120;
        this.expiryTimeSeconds = (System.currentTimeMillis() / 1000);
    }

    @Override
    public char[] getSasToken() {
        long currentTimeSeconds = (System.currentTimeMillis() / 1000);
        try {
            if (this.sasToken == null || this.expiryTimeSeconds + this.renewalBufferSeconds >= currentTimeSeconds) {
                this.sasToken = stsGetToken();
                assert this.sasToken != null;
                String t = String.copyValueOf(this.sasToken);
                String[] bits = t.split("SE=");
                long l = Long.parseLong(bits[1]);
                this.expiryTimeSeconds = l; // the SE= number
                this.renewalBufferSeconds = (int)(l * 0.15); // renew within 15% of expiry
            }
        } catch (InterruptedException | IOException e) {
            e.printStackTrace();
        }
        return this.sasToken;
    }

    private char[] stsGetToken() throws IOException, InterruptedException {
        String stsUrl = String.format("http://localhost:8080/sts/azure/token/operations?sr=%s/devices/%s", this.hostname, this.deviceId);
        HttpRequest request = HttpRequest.newBuilder()
            .uri(URI.create(stsUrl))
            .timeout(Duration.ofMinutes(2))
            .header("Content-Type", "application/json")
            .build();
        HttpClient client = HttpClient.newBuilder()
            .version(HttpClient.Version.HTTP_1_1)
            .connectTimeout(Duration.ofSeconds(20))
            .build();
        HttpResponse < String > response = client.send(request, HttpResponse.BodyHandlers.ofString());
        if (response.statusCode() < 200 || response.statusCode() >= 300) {
            return null;
        }
        if (response.body().isEmpty()) {
            return null;
        }
        return response.body().toCharArray();
    }
}

適用於 Java Azure IoT 中樞 裝置 SDK 的 SAS 令牌驗證實作摘要:

  1. 在類別上實作 SasTokenProvider 介面,並包含令牌更新功能。

  2. 實作 連線 ionStatusChangeCallback 處理程式來擷取傳輸連線狀態變更,並避免傳輸實作引發的例外狀況。

參考:

Azure IoT 中樞 裝置 SDK for Python

適用於 Python 的 Azure IoT 中樞 裝置 SDK 會透過 IoTHubDeviceClient 物件上的方法實作 SAS 令牌支援。 這些方法可讓您使用令牌建立裝置用戶端,以及在建立裝置客戶端之後提供更新的令牌。 它們不會實作令牌存留期管理,但可以輕易地實作為異步操作。

Python 3.7 範例實作,只顯示功能大綱:

import asyncio
import iothub_device_client

async def main():
    # Get a SAS token you generated
    sastoken = get_new_sastoken()
    # The client object is used to interact with your Azure IoT Hub.
    device_client = iothub_device_client.create_from_sastoken(sastoken)

    # Connect the client
    await device_client.connect()

    # Define behavior for providing new SAS tokens to prevent expiry
    async def sastoken_keepalive():
        while True:
            await asyncio.sleep(new_token_interval)
            sastoken = get_new_sastoken()
            await device_client.update_sastoken(sastoken)

    # Also run the SAS token keepalive in the event loop
    keepalive_task = asyncio.create_task(sastoken_keepalive())

    # Cancel the SAS token update task
    keepalive_task.cancel()

    # Finally, shut down the client
    await device_client.shutdown()

if __name__ == "main":
    asyncio.run(main())

適用於 Python SAS 令牌驗證的裝置 SDK Azure IoT 中樞 摘要:

  1. 建立 SAS 令牌產生函式。

  2. 使用 IoTHubDeviceClient.create_from_sastoken 建立裝置用戶端。

  3. 以個別活動的形式管理令牌存留期,並視IoTHubDeviceClient.update_sastoken方法需要提供更新的令牌給裝置用戶端。

參考:

Azure IoT 中樞 裝置 SDK for Node.JS/JavaScript

適用於 Node.JS/JavaScript 的 Azure IoT 會實作 SharedAccessSignatureAuthenticationProvider,以向裝置用戶端提供 SAS 令牌,並傳輸以向 IoT 中樞 進行驗證。 它不會實作任何令牌更新功能。 裝置應用程式必須管理令牌存留期,並視需要更新令牌。

使用SharedAccessSignature 和 updateSharedAccessSignature 的裝置用戶端方法,起始與 IoT 中樞 的連線,並將更新的令牌提供給 SharedAccessSignatuteAuthenticationProvider,這會導致驗證提供者向傳輸發出 newTokenAvailable 事件。

simple_sample_device_with_sas.js範例中會提供基本的 SAS 令牌範例。

適用於 Node.JS/JavaScript 的 Azure IoT 中樞 裝置 SDK 摘要:

  1. 實作 SAS 令牌存留期管理和更新。

  2. 使用SharedAccessSignature的裝置用戶端來建構裝置客戶端實例。

  3. 使用裝置用戶端 updateSharedAccessSignature 來提供更新的令牌。

參考:

下一步