https://d226lax1qjow5r.cloudfront.net/blog/blogposts/build-a-2fa-server-with-kotlin-and-ktor/kotlin_ktor_2fa_1200x600_white.png

Erstellen eines 2FA-Servers mit Kotlin und Ktor

Zuletzt aktualisiert am January 20, 2021

Lesedauer: 6 Minuten

In diesem Lernprogramm werden Sie einen Server schreiben, der eine API für Zwei-Faktoren-Authentifizierung (2FA). Diese API wird es Desktop-Clients, mobilen Clients und Web-Clients ermöglichen, die Zwei-Faktor-Authentifizierung zu nutzen.

Um die Anwendung zu erstellen, werden Sie die Kotlin Sprache und Ktorein asynchrones Framework zur Erstellung von Microservices und Webanwendungen.

Der vollständige Quellcode ist verfügbar auf GitHub.

Voraussetzungen

Für dieses Tutorial benötigen Sie Folgendes:

  • IntelliJ IDEA IDE installiert (kostenpflichtig oder kostenlos, Community Edition).

  • Ktor Plugin für IntelliJ IDEA. Dieses Plugin ermöglicht es Ihnen, ein Ktor-Projekt mit Hilfe eines Assistenten für neue Projekte zu erstellen. Öffnen Sie IntelliJ IDEAund gehen Sie zu Voreinstellungenund dann Pluginsund installieren Sie ein Ktor Plugin aus dem Marktplatz.

Vonage API-Konto

Um dieses Tutorial durchzuführen, benötigen Sie ein Vonage API-Konto. Wenn Sie noch keines haben, können Sie sich noch heute anmelden und mit einem kostenlosen Guthaben beginnen. Sobald Sie ein Konto haben, finden Sie Ihren API-Schlüssel und Ihr API-Geheimnis oben auf dem Vonage-API-Dashboard.

In diesem Lernprogramm wird auch eine virtuelle Telefonnummer verwendet. Um eine zu erwerben, gehen Sie zu Rufnummern > Rufnummern kaufen und suchen Sie nach einer Nummer, die Ihren Anforderungen entspricht.

Ein Ktor-Projekt erstellen

  • Öffnen Sie IntelliJ IDEAund gehen Sie dann zu Datei > Neu > Projekt.

  • Im Neues Projekt Fenster wählen Sie das Ktor Projekt auf der linken Seite aus und drücken Sie die Weiter Schaltfläche.

  • Lassen Sie auf dem nächsten Bildschirm die Standardwerte stehen und drücken Sie die Nächste Taste.

  • Auf dem letzten Bildschirm geben Sie ktor-2fa-server als Anwendungsnamen ein und drücken Sie die Fertigstellen Schaltfläche.

Sie haben ein Ktor-Anwendungsprojekt erstellt.

Erster Endpunkt

Öffnen Sie die src/Application.kt Datei und fügen Sie eine neue routing um zu verifizieren, dass die Anwendung funktioniert:

fun Application.module(testing: Boolean = false) {
    routing {
        get("/") {
            call.respondText("2FA app is working", ContentType.Text.Html)
        }
    }
}

In diesem Lernprogramm wird der gesamte Ktor-Anwendungscode in der Datei Application.kt Datei gespeichert.

Klicken Sie auf den grünen Pfeil neben der main Funktion, um die Anwendung auszuführen (dadurch wird eine neue Ausführungskonfiguration in der IDE erstellt):

Run app

Navigieren Sie zu http://localhost:8080/ in Ihrem Browser, um zu testen, ob die Anwendung korrekt funktioniert - "2FA app is working" sollte angezeigt werden:

App is working

Entwicklungsmodus einstellen

Wenn Sie den Entwicklungsmodus aktivieren, kann die Ktor-Anwendung detailliertere Debugging-Informationen in der IDE anzeigen, z. B. den Aufrufstapel. Dies hilft bei der Entwicklung und der Diagnose von Problemen.

Öffnen Sie die resources/application.conf Datei und fügen Sie development = true:

ktor {
    development = true

    ...

Abhängigkeiten hinzufügen

Vonage Java SDK

Die Sprache Kotlin bietet Interoperabilität mit Javadie es Ihnen ermöglicht, Java-Code aus Kotlin-Code aufzurufen, so dass Sie das Vonage Java SDK für das Kotlin/Ktor-Projekt verwenden können.

Öffnen Sie die build.gradle Datei und fügen Sie die folgende Abhängigkeit hinzu:

dependencies {

    ...

    implementation 'com.vonage:client:6.1.0'
}

Serialisierung

Sie werden JSON als Datenformat für die Kommunikation mit den Clients verwenden. Sie serialisieren Kotlin-Objekte mit Kotlin-Serialisierung.

Öffnen Sie die build.gradle Datei und fügen Sie die folgenden Abhängigkeiten hinzu:

dependencies {

    ...
    
    implementation "io.ktor:ktor-serialization:$ktor_version"
    implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json:1.0.1'
}

Die Kotlin-Serialisierungsbibliothek verwendet Preprocessing (zur Kompilierzeit), daher müssen Sie das org.jetbrains.kotlin.plugin.serialization Gradle-Plugin hinzufügen. Zum Zeitpunkt der Erstellung dieses Artikels verwendet Ktor die alte Methode der Anwendung von Gradle-Pluginsverwendet, also müssen wir sie durch die neue Konfiguration ersetzen.

Öffnen Sie die build.gradle Datei und entfernen Sie Plugins:

apply plugin: 'kotlin'
apply plugin: 'application'

Entfernen Sie die mainClassName:

mainClassName = "io.ktor.server.netty.EngineMain"

Entfernen Sie die classpath:

dependencies {
    classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}

Fügen Sie Plugins mit der neuen Gradle-Syntax hinzu, direkt unter buildscript Block:

buildscript {
    // ...
}

plugins {
    id "java"
    id "org.jetbrains.kotlin.jvm" version "$kotlin_version"
    id "org.jetbrains.kotlin.plugin.serialization" version "$kotlin_version"
}

Nach all diesen Änderungen sollte die build.gradle Datei wie folgt aussehen:

buildscript {
    repositories {
        jcenter()
    }

    dependencies {
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
    }
}

plugins {
    id "java"
    id "org.jetbrains.kotlin.jvm" version "$kotlin_version"
    id "org.jetbrains.kotlin.plugin.serialization" version "$kotlin_version"
}

group 'com.example'
version '0.0.1'

sourceSets {
    main.kotlin.srcDirs = main.java.srcDirs = ['src']
    test.kotlin.srcDirs = test.java.srcDirs = ['test']
    main.resources.srcDirs = ['resources']
    test.resources.srcDirs = ['testresources']
}

repositories {
    mavenLocal()
    jcenter()
}

dependencies {
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
    implementation "io.ktor:ktor-server-netty:$ktor_version"
    implementation "ch.qos.logback:logback-classic:$logback_version"
    testImplementation "io.ktor:ktor-server-tests:$ktor_version"

    implementation 'com.vonage:client:6.1.0'
    implementation "io.ktor:ktor-serialization:$ktor_version"
    implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json:1.0.1'
}

Die kotlin_version und ktor_version sind in der Datei gradle.properties Datei definiert.

Um die Serialisierung zu ermöglichen, muss der JSON-Konverter für die Ktor-Anwendung aktiviert werden. Öffnen Sie die Application.kt Datei und fügen Sie einen install Block innerhalb Application.module Funktion:

fun Application.module(testing: Boolean = false) {

    install(ContentNegotiation) {
        json()
    }
    
    // ...
}

Die IDE markiert alle Klassen und Erweiterungen, bei denen der Import fehlt, mit roter Farbe. Fahren Sie mit dem Rollover über den Klassen- oder Methodennamen, warten Sie bis ein Fenster erscheint und wählen Sie import... um den Klassenimport hinzuzufügen und den Fehler zu beheben.

Erstellen einer Vonage-Anwendung

Eine Vonage-Anwendung bietet 2FA-Funktionen für die API. Erstellen Sie eine Vonage-Anwendung im Dashboard. Klicken Sie auf die Schaltfläche Erstellen einer neuen Anwendung und geben Sie einen Namen ein, dann klicken Sie auf die Schaltfläche Neue Anwendung generieren Schaltfläche.

Gehen Sie zu Einstellungen und notieren Sie sich API key und API secret.

Vonage-Client initialisieren

Fügen Sie die client Eigenschaft innerhalb der Application.module Funktion ein, um einen Vonage-Client zu initialisieren:

fun Application.module(testing: Boolean = false) {

    val client: VonageClient = VonageClient.builder()
        .apiKey("API_KEY")
        .apiSecret("API_SECRET")
        .build()

    install(ContentNegotiation) {
        json()
    }

    // ...
}

Ersetzen Sie API_KEY und API_SECRET mit den Werten aus dem Dashboard.

ANMERKUNG: in Produktion API_KEY und API_SECRET sollten aus Umgebungsvariablen.

API-Funktionalität

Sie werden zwei API-Endpunkte erstellen:

  • verifyNumber - wird der Client zunächst diesen Endpunkt ansteuern, um den Verifizierungsprozess durch Verarbeitung der zu verifizierenden Telefonnummer zu starten.

  • verifyCode - Nach Erhalt des Codes (per SMS oder Sprachanruf) sendet der Client den Code, und die Anwendung führt eine 2FA-Prüfung durch, um festzustellen, ob der Client verifiziert ist.

API-Endpunkt verifyNumber erstellen

Definieren Sie einen neuen Routen-Handler, get("/verifyNumber"), innerhalb des routing Block der Application.module Funktion:

fun Application.module(testing: Boolean = false) {

    // ...

    routing {
        get("/") {
            call.respondText("2FA app is working", ContentType.Text.Html)
        }
        get("/verifyNumber") {
            // ...
        }
    }
}

Der Code innerhalb des get("/verifyNumber") Route-Handler wird ausgeführt, wenn der Client die http://localhost:8080/verifyNumber URL MACHT.

Der verifyNumber Endpunkt wird die folgende Logik enthalten:

  • abrufen phoneNumber Parameter aus dem Abfrage-String (http://localhost:8080/verifyNumber?phoneNumber=1234)

  • 2FA-Verifizierung mit dem Vonage SDK starten

  • Rückgabe requestId als JSON (in einer Produktionsanwendung würden Sie die ID normalerweise auf der Serverseite speichern)

Fügen Sie die folgende Logik in den get("/verifyNumber") Routen-Handler hinzu:

get("/verifyNumber") {
    val phoneNumber = call.parameters["phoneNumber"]
    require(!phoneNumber.isNullOrBlank()) { "phoneNumber is missing" }

    val ongoingVerify = client.verifyClient.verify(phoneNumber, "VONAGE")

    val response = VerifyNumberResponse(ongoingVerify.requestId)
    call.respond(response)
}

Definieren Sie eine VerifyNumberResponse Klasse, die in JSON serialisiert und an den API-Client zurückgegeben werden soll. Fügen Sie den folgenden Code am Ende der Application.kt Datei hinzu:

@Serializable
data class VerifyNumberResponse(val requestId: String)

Kotlin erlaubt die Definition mehrerer Top-Level-Member (Klassen, Eigenschaften usw.) in einer einzigen Datei.

Aufgrund eines Fehler im Kotlin-Plugin müssen Sie die Import-Anweisung für Serializable Annotation manuell hinzufügen. Fügen Sie den folgenden Code am Anfang der Datei ein, direkt unter der letzten Import-Anweisung:

import kotlinx.serialization.Serializable

Anstatt die integrierte Verifizierung von Vonage zu verwenden, könnten Sie den Code selbst generieren und eine SMS mit dem Java-SDK von Vonage versenden. Der Verifizierungsmechanismus von Vonage bietet jedoch eine einfache Möglichkeit zur Verwendung komplexerer Arbeitsabläufez.B.: Der Standard-Workflow tätigt einen Anruf und liest dem Benutzer den Code vor, wenn der Kunde den SMS-Code nicht innerhalb eines bestimmten Zeitraums übermittelt hat.

API-Endpunkt verifyCode erstellen

Definieren Sie einen neuen Routen-Handler, get("/verifyCode"), innerhalb des routing Block der Application.module Funktion:

fun Application.module(testing: Boolean = false) {

    // ...

    routing {
        // ...
        get("/verifyCode") {
            // ...
        }
    }
}

Der verifyCode Endpunkt wird die folgende Logik enthalten:

  • abrufen code Parameter aus dem Query-String (code wird dem Nutzer nach Aufruf des verifyNumber Endpunkt)

  • Abrufen eines Verifizierungs requestId Parameter aus dem Abfrage-String (Wert abgerufen von verifyNumber Endpunkt)

  • Code mit Vonage SDK verifizieren

  • Rückgabe des Verifizierungsstatus an den Kunden

Fügen Sie die folgende Logik in den get("/verifyCode") Routen-Handler hinzu:

get("/verifyCode") {
    val code = call.parameters["code"]
    val requestId = call.parameters["requestId"]

    val checkResponse = client.verifyClient.check(requestId, code)
    println(checkResponse.status)

    val status = if(checkResponse.status == VerifyStatus.OK) {
        "OK"
    } else {
        "ERROR: ${checkResponse.status}"
    }

    val response = VerifyCodeResponse(status)
    call.respond(response)
}

Definieren Sie eine VerifyCodeResponse Klasse, die in JSON serialisiert und an den API-Client zurückgegeben werden soll. Fügen Sie den folgenden Code am Ende der Application.kt Datei hinzu:

@Serializable
data class VerifyCodeResponse(val status: String)

Nach all diesen Änderungen sollte die Datei, Application.kt sollte die Datei wie folgt aussehen:

package com.example

import com.vonage.client.VonageClient
import com.vonage.client.verify.VerifyStatus
import io.ktor.application.*
import io.ktor.features.*
import io.ktor.http.*
import io.ktor.response.*
import io.ktor.routing.*
import io.ktor.serialization.*
import kotlinx.serialization.Serializable

fun main(args: Array<String>): Unit = io.ktor.server.netty.EngineMain.main(args)

@Suppress("unused") // Referenced in application.conf
@kotlin.jvm.JvmOverloads
fun Application.module(testing: Boolean = false) {

    val client: VonageClient = VonageClient.builder()
        .apiKey("API_KEY")
        .apiSecret("API_KEY")
        .build()

    install(ContentNegotiation) {
        json()
    }

    routing {
        get("/") {
            call.respondText("2FA app is working", ContentType.Text.Html)
        }
        get("/verifyNumber") {
            val phoneNumber = call.parameters["phoneNumber"]
            require(!phoneNumber.isNullOrBlank()) { "phoneNumber is missing" }

            val ongoingVerify = client.verifyClient.verify(phoneNumber, "VONAGE")
            val response = VerifyNumberResponse(ongoingVerify.requestId)
            call.respond(response)
        }
        get("/verifyCode") {
            val code = call.parameters["code"]
            val requestId = call.parameters["requestId"]

            val checkResponse = client.verifyClient.check(requestId, code)
            println(checkResponse.status)

            val status = if(checkResponse.status == VerifyStatus.OK) {
                "OK"
            } else {
                "ERROR: ${checkResponse.status}"
            }

            val response = VerifyCodeResponse(status)
            call.respond(response)
        }
    }
}

@Serializable
data class VerifyNumberResponse(val requestId: String)

@Serializable
data class VerifyCodeResponse(val status: String)

Verwenden Sie die API

Die API-Implementierung ist abgeschlossen, also können wir sie testen.

Jeder Client kann die API nutzen, auch Desktop- und mobile Clients, aber Sie werden einfache Tests mit einem Webbrowser durchführen.

Starten Sie die Anwendung Ktor.

Ersetzen Sie PHONE_NUMBER durch eine aktuelle Telefonnummer und öffnen Sie die folgende URL im Browser:

http://localhost:8080/verifyNumber?phoneNumber=PHONE_NUMBER

Vonage Telefonnummern sind in E.164 Format, '+' und '-' sind nicht gültig. Vergewissern Sie sich, dass Sie bei der Eingabe Ihrer Numbers die Landesvorwahl angeben, z. B. US: 14155550100 und UK: 447700900001

Als Testnutzerkönnen Sie nur SMS versenden und Voice-Anrufe an die Nummer, mit der Sie sich registriert haben, sowie an bis zu 4 weitere Testnummern Ihrer Wahl tätigen (Sie können Ihr Vonage-Konto aufladen, um diese Beschränkung aufzuheben).

Sie sollten eine SMS mit einem Code erhalten und eine ähnliche Antwort sehen:

{"requestId":"9ac76db7971b4ea4a49f2e061432c6fe"}

Verfassen Sie eine zweite Anfrage. Ersetzen Sie REQUEST_ID durch den vom Server zurückgegebenen Wert (im obigen Beispiel ist es 9ac76db7971b4ea4a49f2e061432c6fe) und ersetzen Sie CODE durch den erhaltenen Verifizierungscode:

http://localhost:8080/verifyCode?requestId=REQUEST_ID&code=CODE

Wenn die Telefonnummer des Kunden verifiziert ist, sollten Sie die folgende Antwort erhalten:

{"status":"OK"}

Sie verwenden einen standardmäßigen Vonage-Verifizierungs-Workflow (/verify/verify-v1/guides/workflows-and-events). Wenn Sie den Code also nicht innerhalb von 125 Sekunden eingeben, erhalten Sie einen Sprachanruf, der den Code vorliest.

Weitere Lektüre

Sie finden den in diesem Tutorial gezeigten Code auf der Github.

Im Folgenden finden Sie einige weitere Tutorials, die wir für die Nutzung unserer Dienste mit Go geschrieben haben:

Wenn Sie Fragen, Ratschläge oder Ideen haben, die Sie mit der Community teilen möchten, können Sie sich gerne in unserem Slack-Arbeitsbereich der Gemeinschaft. Ich würde mich freuen, von allen zu hören, die dieses Tutorial implementiert haben und wie Ihr Projekt funktioniert.

Teilen Sie:

https://a.storyblok.com/f/270183/384x384/8ae5af43bb/igor-wojda.png
Igor WojdaVonage Ehemalige