빠른 보안 CoAP 클라이언트

CoAP 클라이언트를 보호하고 Qt Quick 사용자 인터페이스와 함께 사용하기.

빠른 보안 Co AP 클라이언트는 보안 CoAP 클라이언트를 만들고 Qt Quick 애플리케이션에서 사용하는 방법을 보여줍니다.

참고: Qt CoAP 는 현재 버전에서 QML API를 제공하지 않습니다. 그러나 예제에 표시된 대로 모듈의 C++ 클래스를 QML에서 사용할 수 있도록 만들 수 있습니다.

예제 실행하기

에서 예제를 실행하려면 Qt Creator에서 Welcome 모드를 열고 Examples 에서 예제를 선택합니다. 자세한 내용은 예제 빌드 및 실행하기를 참조하세요.

예제 애플리케이션을 실행하려면 먼저 보안 CoAP 서버를 설정해야 합니다. PSK(사전 공유 키) 또는 인증서 인증 모드 중 하나를 지원하는 모든 보안 CoAP 서버로 예제를 실행할 수 있습니다. 보안 CoAP 서버 설정에 대한 자세한 내용은 보안 CoAP 서버 설정하기를 참조하세요.

C++ 클래스를 QML에 노출하기

이 예에서는 QCoapClient 클래스와 QtCoap 네임스페이스를 QML에 노출해야 합니다. 이를 위해 사용자 지정 래퍼 클래스를 만들고 특수 등록 매크로를 사용합니다.

QmlCoapSecureClient 클래스를 QCoapClient 을 감싸는 래퍼로 만듭니다. 이 클래스는 선택한 보안 모드 및 보안 구성 매개변수도 보유합니다. Q_INVOKABLE 매크로를 사용하여 여러 메서드를 QML에 노출하세요. 또한 QML_NAMED_ELEMENT 매크로를 사용하여 QML에 해당 클래스를 CoapSecureClient 으로 등록합니다.

class QmlCoapSecureClient : public QObject
{
    Q_OBJECT
    QML_NAMED_ELEMENT(CoapSecureClient)

public:
    QmlCoapSecureClient(QObject *parent = nullptr);
    ~QmlCoapSecureClient() override;

    Q_INVOKABLE void setSecurityMode(QtCoap::SecurityMode mode);
    Q_INVOKABLE void sendGetRequest(const QString &host, const QString &path, int port);
    Q_INVOKABLE void setSecurityConfiguration(const QString &preSharedKey, const QString &identity);
    Q_INVOKABLE void setSecurityConfiguration(const QString &localCertificatePath,
                                              const QString &caCertificatePath,
                                              const QString &privateKeyPath);
    Q_INVOKABLE void disconnect();

Q_SIGNALS:
    void finished(const QString &result);

private:
    QCoapClient *m_coapClient;
    QCoapSecurityConfiguration m_configuration;
    QtCoap::SecurityMode m_securityMode;
};

그런 다음 QtCoap 네임스페이스를 등록하여 여기에 제공된 열거 형을 사용할 수 있도록 합니다:

namespace QCoapForeignNamespace
{
    Q_NAMESPACE
    QML_FOREIGN_NAMESPACE(QtCoap)
    QML_NAMED_ELEMENT(QtCoap)
}

빌드 파일 조정하기

QML에서 사용자 지정 유형을 사용할 수 있도록 하려면 빌드 시스템 파일을 적절히 업데이트하세요.

CMake

CMake 기반 빌드의 경우 CMakeLists.txt 에 다음을 추가합니다:

qt_add_qml_module(quicksecureclient
    URI CoapSecureClientModule
    SOURCES
        qmlcoapsecureclient.cpp qmlcoapsecureclient.h
    QML_FILES
        FilePicker.qml
        Main.qml
)
qmake

qmake 기반 빌드의 경우 quicksecureclient.pro 파일을 다음과 같은 방법으로 수정합니다:

CONFIG += qmltypes
QML_IMPORT_NAME = CoapSecureClientModule
QML_IMPORT_MAJOR_VERSION = 1
    ...
qml_resources.files = \
    qmldir \
    FilePicker.qml \
    Main.qml

qml_resources.prefix = /qt/qml/CoapSecureClientModule

RESOURCES += qml_resources

새 QML 유형 사용

이제 C++ 클래스가 QML에 제대로 노출되면 새 유형을 사용할 수 있습니다.

클라이언트 생성

CoapSecureClientMain.qml 파일에서 인스턴스화됩니다. 이 인스턴스는 QmlCoapSecureClient::finished() 신호를 처리하고 그에 따라 UI를 업데이트합니다:

CoapSecureClient {
    id: client
    onFinished: (result) => {
        outputView.text = result;
        statusLabel.text = "";
        disconnectButton.enabled = true;
    }
}

QCoapClient 인스턴스는 사용자가 UI에서 보안 모드를 선택하거나 변경할 때 생성됩니다. QmlCoapSecureClient::setSecurityMode() 메서드는 보안 모드 중 하나를 선택하면 QML 코드에서 호출됩니다:

ButtonGroup {
    id: securityModeGroup
    onClicked: {
        if ((securityModeGroup.checkedButton as RadioButton) === preSharedMode)
            client.setSecurityMode(QtCoap.SecurityMode.PreSharedKey);
        else
            client.setSecurityMode(QtCoap.SecurityMode.Certificate);
    }
}

C++ 측에서 이 메서드는 QCoapClient 를 생성하고 finished() 및 error() 신호에 연결합니다. 이 클래스는 내부적으로 두 신호를 모두 처리한 후 새로운 finished() 신호로 전달합니다.

void QmlCoapSecureClient::setSecurityMode(QtCoap::SecurityMode mode)
{
    // Create a new client, if the security mode has changed
    if (m_coapClient && mode != m_securityMode) {
        delete m_coapClient;
        m_coapClient = nullptr;
    }

    if (!m_coapClient) {
        m_coapClient = new QCoapClient(mode);
        m_securityMode = mode;

        connect(m_coapClient, &QCoapClient::finished, this,
                [this](QCoapReply *reply) {
                    if (!reply)
                        emit finished(tr("Something went wrong, received a null reply"));
                    else if (reply->errorReceived() != QtCoap::Error::Ok)
                        emit finished(errorMessage(reply->errorReceived()));
                    else
                        emit finished(reply->message().payload());
                });

        connect(m_coapClient, &QCoapClient::error, this,
                [this](QCoapReply *, QtCoap::Error errorCode) {
                    emit finished(errorMessage(errorCode));
                });
    }
}
요청 보내기

Send Request 버튼을 클릭하여 선택한 보안 모드에 따라 보안 구성을 설정하고 GET 요청을 보냅니다:

Button {
    id: requestButton
    text: qsTr("Send Request")
    enabled: securityModeGroup.checkState !== Qt.Unchecked

    onClicked: {
        outputView.text = "";
        if ((securityModeGroup.checkedButton as RadioButton) === preSharedMode)
            client.setSecurityConfiguration(pskField.text, identityField.text);
        else
            client.setSecurityConfiguration(localCertificatePicker.selectedFile,
                                            caCertificatePicker.selectedFile,
                                            privateKeyPicker.selectedFile);

        client.sendGetRequest(hostComboBox.editText, resourceField.text,
                              parseInt(portField.text));

        statusLabel.text = qsTr("Sending request to %1%2...").arg(hostComboBox.editText)
                                                             .arg(resourceField.text);
    }
}

setSecurityConfiguration 방법에는 두 가지 오버로드가 있습니다.

PSK 모드에 대한 오버로드는 단순히 클라이언트 ID와 사전 공유 키를 설정합니다:

void
QmlCoapSecureClient::setSecurityConfiguration(const QString &preSharedKey, const QString &identity)
{
    QCoapSecurityConfiguration configuration;
    configuration.setPreSharedKey(preSharedKey.toUtf8());
    configuration.setPreSharedKeyIdentity(identity.toUtf8());
    m_configuration = configuration;
}

그리고 X.509 인증서에 대한 오버로드는 인증서 파일과 개인 키를 읽고 보안 구성을 설정합니다:

void QmlCoapSecureClient::setSecurityConfiguration(const QString &localCertificatePath,
                                                   const QString &caCertificatePath,
                                                   const QString &privateKeyPath)
{
    QCoapSecurityConfiguration configuration;

    const auto localCerts =
            QSslCertificate::fromPath(QUrl(localCertificatePath).toLocalFile(), QSsl::Pem,
                                      QSslCertificate::PatternSyntax::FixedString);
    if (localCerts.isEmpty())
        qCWarning(lcCoapClient, "The specified local certificate file is not valid.");
    else
        configuration.setLocalCertificateChain(localCerts.toVector());

    const auto caCerts = QSslCertificate::fromPath(QUrl(caCertificatePath).toLocalFile(), QSsl::Pem,
                                                   QSslCertificate::PatternSyntax::FixedString);
    if (caCerts.isEmpty())
        qCWarning(lcCoapClient, "The specified CA certificate file is not valid.");
    else
        configuration.setCaCertificates(caCerts.toVector());

    QFile privateKey(QUrl(privateKeyPath).toLocalFile());
    if (privateKey.open(QIODevice::ReadOnly)) {
        QCoapPrivateKey key(privateKey.readAll(), QSsl::Ec);
        configuration.setPrivateKey(key);
    } else {
        qCWarning(lcCoapClient) << "Unable to read the specified private key file"
                                << privateKeyPath;
    }
    m_configuration = configuration;
}

보안 구성을 설정한 후 sendGetRequest 메서드는 요청 URL을 설정하고 GET 요청을 보냅니다:

void QmlCoapSecureClient::sendGetRequest(const QString &host, const QString &path, int port)
{
    if (!m_coapClient)
        return;

    m_coapClient->setSecurityConfiguration(m_configuration);

    QUrl url;
    url.setHost(host);
    url.setPath(path);
    url.setPort(port);
    m_coapClient->get(url);
}

첫 번째 요청을 보낼 때 CoAP 서버와의 핸드셰이크가 수행됩니다. 핸드셰이크가 성공적으로 완료되면 이후의 모든 메시지는 암호화되며, 핸드셰이크가 성공한 후에는 보안 구성을 변경해도 아무런 영향을 미치지 않습니다. 보안 구성을 변경하거나 호스트를 변경하려면 먼저 연결을 끊어야 합니다.

void QmlCoapSecureClient::disconnect()
{
    if (m_coapClient)
        m_coapClient->disconnect();
}

그러면 핸드셰이크가 중단되고 열려 있던 소켓이 닫힙니다.

X.509 인증서를 사용하는 인증의 경우 인증서 파일을 지정해야 합니다. 이를 위해 FilePicker 컴포넌트가 사용됩니다. 텍스트 필드와 버튼을 누르면 파일 대화 상자를 여는 버튼이 결합되어 있습니다:

Item {
    id: filePicker

    property string dialogText
    property alias selectedFile: filePathField.text

    height: addFileButton.height

    FileDialog {
        id: fileDialog
        title: qsTr("Please Choose %1").arg(filePicker.dialogText)
        currentFolder: StandardPaths.writableLocation(StandardPaths.HomeLocation)
        fileMode: FileDialog.OpenFile
        onAccepted: filePathField.text = fileDialog.selectedFile
    }

    RowLayout {
        anchors.fill: parent
        TextField {
            id: filePathField
            placeholderText: qsTr("<%1>").arg(filePicker.dialogText)
            inputMethodHints: Qt.ImhUrlCharactersOnly
            selectByMouse: true
            Layout.fillWidth: true
        }

        Button {
            id: addFileButton
            text: qsTr("Add %1").arg(filePicker.dialogText)
            onClicked: fileDialog.open()
        }
    }
}

FilePicker 는 인증서 및 개인 키에 대한 입력 필드를 만들기 위해 Main.qml 파일에서 여러 번 인스턴스화됩니다:

FilePicker {
    id: localCertificatePicker
    dialogText: qsTr("Local Certificate")
    enabled: (securityModeGroup.checkedButton as RadioButton) === certificateMode
    Layout.columnSpan: 2
    Layout.fillWidth: true
}

FilePicker {
    id: caCertificatePicker
    dialogText: qsTr("CA Certificate")
    enabled: (securityModeGroup.checkedButton as RadioButton) === certificateMode
    Layout.columnSpan: 2
    Layout.fillWidth: true
}

FilePicker {
    id: privateKeyPicker
    dialogText: qsTr("Private Key")
    enabled: (securityModeGroup.checkedButton as RadioButton) === certificateMode
    Layout.columnSpan: 2
    Layout.fillWidth: true
}

보안 CoAP 서버 설정

이 예제를 실행하려면 PSK 또는 인증서 모드(또는 둘 다)를 지원하는 보안 CoAP 서버가 있어야 합니다. 다음과 같은 옵션이 있습니다:

  • libcoap, Californium, FreeCoAP 또는 DTLS를 지원하는 기타 CoAP 라이브러리 등을 사용하여 보안 CoAP 서버를 수동으로 빌드하고 실행합니다.
  • 이 예제에 적합한 보안 CoAP 서버를 빌드하고 실행하는 Docker Hub에서 제공되는 준비된 Docker 이미지를 사용합니다. 도커 기반 CoAP 서버를 사용하는 데 필요한 단계는 아래에 설명되어 있습니다.
PSK 모드용 서버 설정하기

다음 명령은 캘리포니아 플러그테스트 (기본적으로 보안되지 않음) 기반의 보안 CoAP 서버용 도커 컨테이너를 Docker Hub에서 가져와서 시작합니다:

docker run --name coap-test-server -d --rm -p 5683:5683/udp -p 5684:5684/udp tqtc/coap-californium-test-server:3.8.0

포트 5683 (비보안) 및 5684 (보안)에서 CoAP 테스트 서버에 연결할 수 있습니다. IP 주소 검색에 대한 지침은 IP 주소 가져오기를 참조하세요.

이 서버로 예제를 실행하려면 미리 공유한 키를 secretPSK 로 설정하고 ID를 Client_identity 로 설정해야 합니다.

인증서 모드를 위한 서버 설정하기

X.509 인증서로 인증을 사용하는 보안 서버의 도커 이미지는 FreeCoAP 라이브러리의 시간 서버 예제를 기반으로 합니다. 다음 명령은 도커 허브에서 컨테이너를 가져와서 시작합니다:

docker run --name coap-time-server -d --rm -p 5684:5684/udp tqtc/coap-secure-time-server:freecoap

IP 주소 가져오기에 대한 지침은 IP 주소 가져오기를 참조하세요. 검색된 IP 주소는 포트 5684 및 리소스 경로 /time 에서 CoAP 테스트 서버에 연결할 수 있습니다.

이 서버로 예제를 실행하려면 서버에 필요한 인증서 파일을 지정해야 합니다. 인증서 파일은 도커 컨테이너의 /root/certs 디렉토리에 있습니다. 로컬 디렉터리에 복사하려면 다음 명령을 사용합니다:

docker cp <container_id>:/root/certs <local_directory_path>

예를 들어

$ docker cp 5e46502df88f:/root/certs ~/

컨테이너 ID를 얻는 방법은 아래에 설명되어 있습니다.

IP 주소 가져오기

도커 컨테이너의 IP 주소를 찾으려면 먼저 docker ps 명령을 실행하여 컨테이너 ID를 검색하면 다음과 같은 내용이 출력됩니다:

$ docker ps
CONTAINER ID        IMAGE
5e46502df88f        tqtc/coap-californium-test-server:3.8.0

그런 다음 다음 명령을 사용하여 IP 주소를 얻을 수 있습니다:

docker inspect <container_id> | grep IPAddress

예를 들어

$ docker inspect 5e46502df88f | grep IPAddress
...
"IPAddress": "172.17.0.2",
...
Docker 컨테이너 종료하기

사용 후 Docker 컨테이너를 종료하려면 다음 명령을 사용합니다:

docker stop <container_id>

여기서 <container_id>docker ps 명령으로 검색한 것과 동일합니다.

파일:

© 2025 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.