Stöd för Azure IoT-klient-SDK för tokenservrar från tredje part

Azure IoT
Azure IoT Hub

Artikeln Kontrollera åtkomst till IoT Hub visar hur en tokentjänst från tredje part kan integreras med IoT Hub. Den här artikeln beskriver stödet för sas-tokenautentisering (signatur för delad åtkomst) i var och en av Azure IoT-klient-SDK:erna. Den beskriver också både vad som behöver implementeras i ett enhetsprogram med hjälp av motsvarande SDK för varje språk och hur du använder token med enhetsomfattning eller modulomfattning för principer för delad åtkomst för Enhet Anslut eller Modul Anslut.

Kontext och problem

I den aktuella säkerhetsdokumentationen för Azure IoT Hub beskrivs mönstret för token-server från tredje part för SAS-autentisering med IoT Hub av IoT-enheter med hjälp av Azure IoT-klient-SDK:er. Felaktiga antaganden som gjorts av en kund under ett nyligen genomfördt enterprise-engagemang tyder dock på att du utan ytterligare klargöranden kan utveckla ett missvisande intryck av den supportnivå som implementeras som standard i Azure IoT-klient-SDK:erna.

Den här artikeln beskriver inlärningen av det engagemanget och klargör vad som behöver göras i varje SDK för enheter för att uppnå token-serverautentisering från tredje part. Den här artikeln bör också hindra dig från att göra liknande felaktiga antaganden om stöd för token-servermönstret från tredje part i Azure IoT-klient-SDK.

Lösning

Azure IoT-klient-SDK:er har olika stödnivåer för SAS-tokenautentisering, som var och en kräver viss anpassad kod för att slutföra autentiserings- och tokenhanteringsfunktionerna.

Utvärderingsfrekvensen för token beror på det valda transportprotokollet– MQTT, AMQP eller HTTPS. Variationen beror på protokollets förmåga att stödja proaktiv förnyelse av token och tidsgränser för sessioner. Endast AMQP implementerar proaktiv förnyelsesupport. Det innebär att de andra transporterna stänger anslutningen vid SAS-tokenautentiseringsfel och sedan måste utföra en ny anslutningsåtgärd. Det här är en potentiellt dyr anslutningsåtgärd för klienten.

Om SAS-autentiseringen misslyckas utlöses ett fel av transportimplementeringen som kan hanteras i enhetsprogrammet av händelsehanteraren "Anslut ion Status har ändrats". Om en sådan hanterare inte implementeras stoppas vanligtvis enhetsprogrammet på grund av felet. Med rätt implementering av funktionen för händelsehanterare och tokenförnyelse kan transporterna försöka ansluta igen.

Följande bild illustrerar mönstret token-server från tredje part:

Illustration of the third-party token-server pattern

Följande bild illustrerar implementeringsstöd i Azure IoT-klient-SDK med Mobile Net Operator-integrering:

Flowchart of implementation support in the Azure IoT client SDK with Mobile Net Operator integration

Exempelimplementeringar ingår i Azure Samples-lagringsplatsen på GitHub.

Problem och överväganden

Tänk på följande när du bestämmer dig för om du vill implementera det här mönstret:

  • Klient-SDK:erna för Azure IoT Hub Device Provisioning Service (Azure DPS) stöder inte SAS-tokenautentisering. Azure DPS REST APIstöder SAS-tokenautentisering. För att kunna använda Azure DPS med en tokentjänst från tredje part för SAS-autentisering måste ett enhetsprogram implementera DPS-processen för enheten med hjälp av Azure DPS REST API.

  • Detta består av att göra en första åtgärd för registreringsbegäran och sedan avsöka API:et för driftstatus tills DPS-processen lyckas eller misslyckas. När enheten har lyckats kan du hämta information om enhetsetablering genom att begära dem från Azure DPS REST API Runtime Registration.

Referenser:

När du ska använda det här mönstret

Du bör använda det här mönstret när du vill autentisera till Azure IoT Hub från IoT-enheter med hjälp av de olika Azure IoT-klient-SDK:erna. I stället för att använda klient-SDK:er för SAS-tokenautentisering använder du Azure DPS REST API för att säkerställa implementeringen av proaktivt förnyelsestöd för alla transportmekanismer.

Exempel

Följande avsnitt innehåller exempel som du kan använda för olika programmeringsspråk, till exempel Embedded C, .NET, Java och Python.

Azure IoT Hub-enhets-SDK för C- och Azure IoT Hub-enhets-SDK för Inbäddad C

Följande metod kan användas i enhetsprogram som skapats med Azure IoT C SDK eller Azure IoT Embedded C SDK. Inget av SDK:erna tillhandahåller sas-tokens livslängdshantering, därför måste du implementera en SAS-token lifetime manager-funktion.

SAS-token kan användas via IOTHUB_CLIENT_CONFIG struktur genom att ange deviceSasToken-medlemmen till token och göra deviceKey null. Andra oanvända värden, till exempel protocolGatewayHostName, måste också anges till 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 kan fånga IOTHUB_CLIENT_CONNECTION_STATUS_REASON av IOTHUB_CLIENT_CONNECTION_EXPIRED_SAS_TOKEN för att utlösa en förnyelse av SAS-token via tokentjänsten från tredje part. Detta krävs för att alla transporter ska kunna samla in anslutningsproblem, men krävs specifikt av transporter som inte stöder proaktiv SAS-tokenförnyelse. Proaktiv livslängdshantering för SAS-token kan implementeras som en funktion som körs upprepade gånger under enhetsprogrammens "driftsloop". Se till att livslängden för token utvärderas ofta, och tokenförnyelse kan köras proaktivt när det behövs.

Implementeringssammanfattning för SAS-tokenautentisering för C SDK:er:

  1. Implementera en Anslut ionStatusCallback-hanterare för att samla in IOTHUB_CLIENT_CONNECTION_EXPIRED_SAS_TOKEN händelse- och utlösartokenförnyelse.

  2. Använd en IOTHUB_CLIENT_CONFIG för att ange enhetens SAS-token för att IoTHubDeviceClient_Create.

  3. Implementera proaktiv livslängdshantering för SAS-token som en del av enhetsprogrammets åtgärdsloop.

Azure IoT Hub-enhets-SDK för .NET

Azure IoT-klient-SDK för .NET implementerar stöd för sas-tokens livslängdshantering via den abstrakta Klassen DeviceAuthenticationWithTokenRefresh. En konkret implementering av den här klassen, som lägger till funktioner för tokenförnyelse, kan tillhandahållas som autentiseringsmetod till en DeviceClient.Create-metod. Transportimplementeringarna förnyar token automatiskt via autentiseringsmetoden efter behov. En Anslut ionStatusChangesHandler krävs för att samla in anslutningsändringar och förhindra att undantag utlöses av transporterna.

Exempelimplementering baserat på klassen 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;
    }
}

Implementeringssammanfattning för SAS-tokenautentisering för Azure IoT Hub-enhets-SDK för .NET:

  1. Implementera en konkret klass baserat på den abstrakta klassen DeviceAuthenticationWithTokenRefresh, som implementerar funktioner för tokenförnyelse.

  2. Implementera en Anslut ionStatusChangesHandler för att samla in transportanslutningsstatus och undvika undantag som genereras av transportimplementeringen.

Referenser:

Azure IoT Hub-enhets-SDK för Java

Azure IoT Client SDK för Java implementerar stöd för sas-tokens livslängdshantering via SasTokenProvider-gränssnittet. En klass som implementerar det här gränssnittet med SAS-tokenförnyelsefunktioner kan användas som SecurityProvider i en DeviceClient-konstruktor. Transportimplementeringarna förnyar automatiskt token via säkerhetsprovidern efter behov. En Anslut ionStatusChangeCallback måste registreras för att samla in anslutningsändringar och förhindra att undantag genereras av transporterna.

Exempel på implementering av säkerhetsprovidern som implementerar SasTokenProvider-gränssnittet:

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();
    }
}

Implementering av SAS-tokenautentisering för Azure IoT Hub-enhets-SDK för Java:

  1. Implementera SasTokenProvider-gränssnittet i en klass och inkludera funktioner för tokenförnyelse.

  2. Implementera en Anslut ionStatusChangeCallback-hanterare för att samla in ändringar i transportanslutningsstatusen och undvika undantag som genereras av transportimplementeringen.

Referenser:

Azure IoT Hub-enhets-SDK för Python

Azure IoT Hub-enhetens SDK för Python implementerar SAS-tokenstöd via metoder på IoTHubDeviceClient-objektet. De här metoderna gör det möjligt att skapa en enhetsklient med hjälp av en token och möjligheten att ange en uppdaterad token när enhetsklienten har skapats. De implementerar inte hantering av tokenlivslängd, men det kan enkelt implementeras som en asynkron åtgärd.

Ett Python 3.7-exempelimplementering som bara visar funktionsdispositionen:

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())

Sammanfattning av Azure IoT Hub-enhets-SDK för Python SAS-tokenautentisering:

  1. Skapa SAS-tokengenereringsfunktion.

  2. Skapa en enhetsklient med hjälp av IoTHubDeviceClient.create_from_sastoken.

  3. Hantera tokenlivslängd som en separat aktivitet och förse enhetsklienten med en förnyad token när det krävs av metoden IoTHubDeviceClient.update_sastoken.

Referenser:

Azure IoT Hub-enhets-SDK för Node.JS/JavaScript

Azure IoT för Node.JS/JavaScript implementerar en SharedAccessSignatureAuthenticationProvider som kommer att betjäna en SAS-token till enhetsklienten och transporterar för att autentisera med IoT Hub. Den implementerar inte några funktioner för tokenförnyelse. Enhetsprogrammet måste hantera tokens livslängd och förnya token efter behov.

Använd enhetsklientmetoderna frånSharedAccessSignature och updateSharedAccessSignature för att initiera en anslutning med IoT Hub och ange en förnyad token till SharedAccessSignatuteAuthenticationProvider, vilket gör att autentiseringsprovidern genererar en nyTokenAvailable-händelse till transporterna.

Ett grundläggande SAS-tokenexempel finns i exemplet simple_sample_device_with_sas.js .

Sammanfattning av Azure IoT Hub-enhetens SDK för Node.JS/JavaScript:

  1. Implementera livslängdshantering och förnyelse av SAS-token.

  2. Använd enhetsklienten frånSharedAccessSignature för att konstruera en enhetsklientinstans.

  3. Använd enhetsklientuppdateringSharedAccessSignature för att ange en förnyad token.

Referenser:

Nästa steg