타사 토큰 서버에 대한 Azure IoT 클라이언트 SDK 지원

Azure IoT
Azure IoT Hub

IoT Hub 대한 액세스 제어 문서에서는 타사 토큰 서비스를 IoT Hub와 통합하는 방법을 보여 줍니다. 이 문서에서는 각 Azure IoT 클라이언트 SDK의 SAS(공유 액세스 서명) 토큰 인증에 대한 지원을 간략하게 설명합니다. 또한 각 언어에 해당하는 SDK를 사용하여 디바이스 애플리케이션에서 구현해야 하는 사항과 DeviceConnect 또는 ModuleConnect의 공유 액세스 정책에 디바이스 범위 또는 모듈 범위 토큰을 사용하는 방법을 모두 간략하게 설명합니다.

컨텍스트 및 문제점

현재 Azure IoT Hub 보안 설명서에서는 Azure IoT 클라이언트 SDK를 사용하여 IoT 디바이스에서 IoT Hub를 통해 SAS 인증을 수행하기 위한 타사 토큰 서버 패턴을 살펴봅니다. 그러나 최근 엔터프라이즈 참여 중에 고객이 잘못 추정할 경우 추가적인 확인 없이 Azure IoT 클라이언트 SDK에서 기본적으로 구현되는 지원 수준에 대해 잘못된 선입견을 줄 수 있습니다.

이 문서에서는 해당 참여를 통해 얻는 학습에 대해 설명하고 디바이스가 타사 토큰 서버 인증을 달성하기 위해 각 SDK에서 수행해야 하는 작업을 명확히 설명합니다. 또한 이 문서에서는 Azure IoT 클라이언트 SDK에서 타사 토큰 서버 패턴 지원에 대해 유사한 잘못된 추정을 하지 못하게 해야 합니다.

솔루션

Azure IoT 클라이언트 SDK는 인증 및 토큰 관리 기능을 완료하기 위해 각각에 사용자 지정 코드가 필요한 SAS 토큰 인증에 대한 다양한 수준의 지원을 제공합니다.

토큰 평가 빈도는 선택한 전송 프로토콜(MQTT, AMQP 또는 HTTPS)에 따라 달라집니다. 변형은 토큰 및 세션 시간 제한의 사전 갱신을 지원하는 프로토콜의 기능에 따라 달라집니다. AMQP만 사전 갱신 지원을 구현합니다. 즉, 다른 전송은 SAS 토큰 인증 실패 시 연결을 닫은 다음, 새 연결 작업을 수행해야 합니다. 이것은 클라이언트에 잠재적으로 비용이 많이 드는 연결 작업입니다.

SAS 인증에 실패하면 "연결 상태가 변경됨" 이벤트 처리기를 통해 디바이스 애플리케이션 내에서 처리할 수 있는 전송 구현에서 오류가 발생합니다. 이러한 처리기를 구현하지 못하면 일반적으로 오류로 인해 디바이스 애플리케이션이 중단됩니다. 이벤트 처리기 및 토큰 갱신 기능을 올바르게 구현하면 전송 기능은 연결을 다시 시도할 수 있습니다.

다음 그림에서는 타사 토큰 서버 패턴을 보여 줍니다.

Illustration of the third-party token-server pattern

다음 그림에서는 Mobile Net Operator 통합을 사용하는 Azure IoT 클라이언트 SDK의 구현 지원을 보여 줍니다.

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

샘플 구현은 GitHub의 Azure 샘플 리포지토리에 포함되어 있습니다.

문제 및 고려 사항

이 패턴을 구현할 방법을 결정할 때 다음 사항을 고려하세요.

  • Azure IoT Hub DPS(Device Provisioning Service) 클라이언트 SDK는 SAS 토큰 인증을 지원하지 않습니다. Azure DPS REST API는 SAS 토큰 인증을 “지원합니다”. 따라서 SAS 인증을 위해 타사 토큰 서비스에서 Azure DPS를 사용하려면 디바이스 애플리케이션이 Azure DPS REST API를 사용하여 디바이스 DPS 프로세스를 구현해야 합니다.

  • 이 작업은 초기 등록 요청 작업을 만든 다음, DPS 프로세스가 성공하거나 실패할 때까지 작업 상태 API를 폴링하여 수행합니다. 성공하면 Azure DPS REST API 런타임 등록에서 요청하여 디바이스 프로비저닝 세부 정보를 얻을 수 있습니다.

참조:

이 패턴을 사용해야 하는 경우

다양한 Azure IoT 클라이언트 SDK를 사용하여 IoT 디바이스에서 Azure IoT Hub 인증을 받으려는 경우 이 패턴을 사용해야 합니다. SAS 토큰 인증에 클라이언트 SDK를 사용하는 대신, Azure DPS REST API를 사용하여 모든 전송 메커니즘에 대한 사전 갱신 지원을 구현합니다.

예제

다음 섹션에서는 Embedded C, .NET, Java 및 Python과 같은 다양한 프로그래밍 언어에 사용할 수 있는 예제를 제공합니다.

C용 Azure IoT Hub 디바이스 SDK 및 Embedded C용 Azure IoT Hub 디바이스 SDK

다음 방법은 Azure IoT C SDK 또는 Azure IoT Embedded C SDK를 사용하여 빌드된 디바이스 애플리케이션에서 활용할 수 있습니다. 두 SDK 모두 SAS 토큰 수명 관리를 제공하지 않으므로 SAS 토큰 수명 관리자 기능을 구현해야 합니다.

SAS 토큰은 deviceSasToken 멤버를 토큰으로 설정하고 deviceKey를 null을 만들어 IOTHUB_CLIENT_CONFIG 구조를 통해 사용할 수 있습니다. 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을 catch하여 타사 토큰 서비스를 통해 SAS 토큰 갱신을 트리거할 수 있습니다. 이 작업은 모든 전송에서 연결 문제를 캡처하는 데 필요하지만 사전 SAS 토큰 갱신을 지원하지 않는 전송에 특히 필요합니다. 자동 관리 방식의 SAS 토큰 수명 관리는 디바이스 애플리케이션 "작동" 루프 중에 반복적으로 실행되는 함수로 구현할 수 있습니다. 토큰의 수명이 자주 평가되는지 확인하는 작업과 토큰 갱신을 필요한 경우 사전에 실행할 수 있습니다.

C SDK에 대한 SAS 토큰 인증 구현 요약:

  1. ConnectionStatusCallback 처리기를 구현하여 IOTHUB_CLIENT_CONNECTION_EXPIRED_SAS_TOKEN 이벤트를 캡처하고 토큰 갱신을 트리거합니다.

  2. IOTHUB_CLIENT_CONFIG를 사용하여 IoTHubDeviceClient_Create에 디바이스 SAS 토큰을 제공합니다.

  3. 디바이스 애플리케이션의 작업 루프의 일부로 자동 관리 방식의 SAS 토큰 수명 관리를 구현합니다.

.NET용 Azure IoT Hub 디바이스 SDK

.NET용 Azure IoT 클라이언트 SDK는 추상 DeviceAuthenticationWithTokenRefresh 클래스를 통해 SAS 토큰 수명 관리에 대한 지원을 구현합니다. 토큰 갱신 기능을 추가하는 이 클래스의 구체적인 구현은 DeviceClient.Create 메서드에 인증 방법으로 제공될 수 있습니다. 전송 구현은 필요에 따라 인증 방법을 통해 토큰을 자동으로 갱신합니다. 연결 변경 내용을 캡처하고 전송에서 예외가 발생하지 않도록 하려면 ConnectionStatusChangesHandler가 필요합니다.

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 Hub 디바이스 SDK에 대한 SAS 토큰 인증 구현 요약:

  1. 토큰 갱신 기능을 구현하는 DeviceAuthenticationWithTokenRefresh 추상 클래스를 기준으로 구체적인 클래스를 구현합니다.

  2. ConnectionStatusChangesHandler를 구현하여 전송 연결 상태를 캡처하고 전송 구현에서 발생하는 예외를 방지합니다.

참조:

Java용 Azure IoT Hub 디바이스 SDK:

Java용 Azure IoT 클라이언트 SDK는 SasTokenProvider 인터페이스를 통해 SAS 토큰 수명 관리에 대한 지원을 구현합니다. SAS 토큰 갱신 기능을 사용하여 이 인터페이스를 구현하는 클래스를 DeviceClient 생성자에서 SecurityProvider로 사용할 수 있습니다. 전송 구현은 필요에 따라 보안 공급자를 통해 토큰을 자동으로 갱신합니다. 연결 변경 내용을 캡처하고 전송에서 예외가 발생하지 않도록 하려면 ConnectionStatusChangeCallback을 등록해야 합니다.

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 Hub 디바이스 SDK에 대한 SAS 토큰 인증 구현 요약:

  1. 클래스에서 SasTokenProvider 인터페이스를 구현하고 토큰 갱신 기능을 포함합니다.

  2. ConnectionStatusChangeCallback 처리기를 구현하여 전송 연결 상태 변경 내용을 캡처하고 전송 구현에서 발생하는 예외를 방지합니다.

참조:

Python용 Azure IoT Hub 디바이스 SDK

Python용 Azure IoT Hub 디바이스 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 토큰 인증을 위한 Azure IoT Hub 디바이스 SDK 요약:

  1. SAS 토큰 생성 함수를 만듭니다.

  2. IoTHubDeviceClient.create_from_sastoken을 사용하여 디바이스 클라이언트를 만듭니다.

  3. 토큰 수명을 별도의 작업으로 관리하여 IoTHubDeviceClient.update_sastoken 메서드에 필요한 경우 디바이스 클라이언트에 갱신된 토큰을 제공합니다.

참조:

Node.JS/JavaScript용 Azure IoT Hub 디바이스 SDK

Node.JS/JavaScript용 Azure IoT는 디바이스 클라이언트에 SAS 토큰을 제공하고 IoT Hub에서 인증을 받기 위해 전송을 제공하는 SharedAccessSignatureAuthenticationProvider를 구현합니다. 토큰 갱신 기능은 구현하지 않습니다. 디바이스 애플리케이션은 토큰 수명을 관리하여 필요에 따라 토큰을 갱신해야 합니다.

SharedAccessSignature 및 updateSharedAccessSignature의 디바이스 클라이언트 메서드를 사용하여 IoT Hub와의 연결을 시작하고 SharedAccessSignatuteAuthenticationProvider에 갱신된 토큰을 제공합니다. 그러면 인증 공급자가 newTokenAvailable 이벤트를 전송으로 내보낸다.

기본 SAS 토큰 샘플이 simple_sample_device_with_sas.js 예제에 제공됩니다.

Node.JS/JavaScript용 Azure IoT Hub 디바이스 SDK 요약:

  1. SAS 토큰 수명 관리 및 갱신을 구현합니다.

  2. 디바이스 클라이언트 fromSharedAccessSignature를 사용하여 디바이스 클라이언트 인스턴스를 생성합니다.

  3. 디바이스 클라이언트 updateSharedAccessSignature를 사용하여 갱신된 토큰을 제공합니다.

참조:

다음 단계