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

KotlinとKtorで2FAサーバーを構築する

最終更新日 January 20, 2021

所要時間:1 分

このチュートリアルでは、以下のAPIを提供するサーバーを作成します。 二要素認証 (2FA).このAPIにより、デスクトップクライアント、モバイルクライアント、ウェブクライアントが二要素認証を利用できるようになります。

アプリケーションをビルドするには Kotlin言語と Ktorマイクロサービスやウェブアプリケーションを作成するための非同期フレームワークです。

完全なソースコードは GitHub.

前提条件

このチュートリアルに従うには、以下のものが必要だ:

  • IntelliJ IDEAIDEをインストールする(有料または無料のコミュニティ版)。

  • Ktorプラグインです。このプラグインを使用すると、新規プロジェクトウィザードを使用してKtorプロジェクトを作成できます。開く IntelliJ IDEAを開き 環境設定次に プラグインをインストールし Ktorプラグインをインストールしてください。

Vonage API Account

To complete this tutorial, you will need a Vonage API account. If you don’t have one already, you can sign up today and start building with free credit. Once you have an account, you can find your API Key and API Secret at the top of the Vonage API Dashboard.

This tutorial also uses a virtual phone number. To purchase one, go to Numbers > Buy Numbers and search for one that meets your needs.

Ktorプロジェクトの作成

  • 開く IntelliJ IDEAを開き ファイル > 新規 > プロジェクト.

  • での 新規プロジェクトウィンドウで Ktorプロジェクトを選択し 次へボタンを押します。

  • 次の画面では、デフォルト値のままで 次へボタンを押します。

  • 最後の画面で、アプリケーション名に ktor-2fa-serverを入力し 終了ボタンを押します。

Ktorアプリケーション・プロジェクトを作成しました。

最初の終点

ファイルを開き src/Application.ktファイルを開き、新しい routingを追加し、アプリケーションが動作していることを確認してください:

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

このチュートリアルでは、すべてのKtorアプリケーション・コードを Application.ktファイルに格納されます。

関数の横にある緑色の矢印をクリックします。 main関数の横にある緑色の矢印をクリックしてアプリケーションを実行します(IDEに新しい実行設定が作成されます):

Run app

ブラウザで http://localhost:8080/2FAアプリは動作しています」と表示されるはずです:

App is working

開発モードの設定

開発モードを有効にすると、KtorアプリケーションはIDEでコールスタックなどのより詳細なデバッグ情報を表示できるようになります。開発や問題の診断に役立ちます。

ファイルを開く resources/application.confファイルを開き development = true:

ktor {
    development = true

    ...

依存関係の追加

Vonage Java SDK

Kotlin言語は Javaとの相互運用性を使用できるように、Kotlin コードから Java コードを呼び出すことができます。 Vonage Java SDKを使用できるようになります。

ファイルを開き build.gradleファイルを開き、以下の依存関係を追加する:

dependencies {

    ...

    implementation 'com.vonage:client:6.1.0'
}

連載

クライアントと通信するためのデータ形式としてJSONを使用します。を使用してKotlinオブジェクトをシリアライズします。 Kotlinシリアライゼーション.

ファイルを開き build.gradleファイルを開き、以下の依存関係を追加する:

dependencies {

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

Kotlinシリアライゼーション・ライブラリは(コンパイル時に)前処理を使用するので org.jetbrains.kotlin.plugin.serializationプラグインを追加する必要があります。この記事を書いている時点では、Ktorは Gradleプラグインを適用する古い方法を使用しています。そのため、新しい設定に置き換える必要があります。

ファイルを開いて build.gradleファイルを開き、プラグインを削除する:

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

を削除する。 mainClassName:

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

を削除する。 classpath:

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

新しいGradle構文を使ってプラグインを追加します。 buildscriptブロックに追加します:

buildscript {
    // ...
}

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

すべての修正後 build.gradleファイルは次のようになる:

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'
}

その kotlin_versionktor_versionプロパティは gradle.propertiesファイルで定義されている。

シリアライズを有効にするには、KtorアプリケーションでJSONコンバータを有効にする必要があります。ファイルを開き Application.ktファイルを開き installブロックを追加します。 Application.module関数を追加します:

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

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

IDE はインポートが欠落しているすべてのクラスとエクステンションを赤い色でマークします。クラス名またはメソッド名をロールオーバーし、ウィンドウが表示されるまで待ちます。 import...を選択してクラスのインポートを追加し、エラーを修正します。

Vonageアプリケーションの作成

VonageアプリケーションはAPIに2FA機能を提供する。Vonageアプリケーションを ダッシュボード.をクリックします。 新しいアプリケーションの作成ボタンをクリックし、名前を入力して 新規アプリケーションの作成ボタンをクリックします。

設定 設定API keyそして API secret.

Vonageクライアントの初期化

を追加します。 clientプロパティを Application.module関数を使用してVonageクライアントを初期化します:

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

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

    install(ContentNegotiation) {
        json()
    }

    // ...
}

交換 API_KEYそして API_SECRETの値を使用して ダッシュボード.

注:生産中 API_KEYそして API_SECRET環境変数.

API機能

2つのAPIエンドポイントを構築する:

  • verifyNumber- の場合、クライアントはまずこのエンドポイントをヒットし、検証対象の電話番号を処理して検証プロセスを開始する。

  • verifyCode- SMSまたは音声通話で)コードを受信した後、クライアントはコードを送信し、アプリケーションは2FAチェックを実行し、クライアントがVerifyされているかどうかを判断する。

verifyNumber APIエンドポイントの作成

新しいルートハンドラを定義します、 get("/verifyNumber")を定義します。 routingブロックを定義します。 Application.module関数を定義します:

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

    // ...

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

ルートハンドラ内のコードは、クライアントが get("/verifyNumber")ルートハンドラ内のコードは、クライアントが http://localhost:8080/verifyNumberを呼び出したときに実行されます。

この verifyNumberエンドポイントには以下のロジックが含まれる:

  • 取得 phoneNumberパラメータをクエリー文字列 (http://localhost:8080/verifyNumber?phoneNumber=1234)

  • Vonage SDKを使用して2FA認証を開始する

  • を返します。 requestIdをJSONとして返す(プロダクション・アプリケーションでは、通常はサーバーサイドにIDを保存する)。

以下のロジックを get("/verifyNumber")ルートハンドラに以下のロジックを追加する:

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

クラスを定義する。 VerifyNumberResponseクラスを定義する。このクラスはJSONにシリアライズされ、APIクライアントに返される。以下のコードを Application.ktファイルの末尾に以下のコードを追加します:

@Serializable
data class VerifyNumberResponse(val requestId: String)

Kotlinでは、1つのファイル内で複数のトップレベル・メンバー(クラス、プロパティなど)を定義できる。

バグにより バグKotlinプラグインのバグにより、手動で Serializableアノテーションのimport文を手動で追加する必要があります。ファイルの先頭、最後のimport文のすぐ下に以下のコードを追加する:

import kotlinx.serialization.Serializable

Vonageビルドイン認証を使用する代わりに、自分でコードを生成し、Vonage Java SDKを使用してSMSを送信することができます。しかし、Vonage 検証メカニズムは、より複雑な ワークフロー例:デフォルトのワークフローは、クライアントが特定の期間内にSMSコードを提供しなかった場合、電話をかけ、ユーザーにコードを読み上げます。

verifyCode API エンドポイントの作成

新しいルートハンドラを定義します、 get("/verifyCode")を定義します。 routingブロックを定義します。 Application.module関数を定義します:

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

    // ...

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

この verifyCodeエンドポイントには以下のロジックが含まれる:

  • 取得 codeパラメータは、クエリー文字列 (codeをヒットした後にユーザーに配信されます。 verifyNumberエンドポイント)

  • クエリー文字列から requestIdクエリ文字列から検証パラメータを取得する。 verifyNumberエンドポイントから取得した値)

  • Vonage SDKを使用したコードの検証

  • クライアントに検証ステータスを返す

以下のロジックを get("/verifyCode")ルートハンドラに以下のロジックを追加する:

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

クラスを定義する。 VerifyCodeResponseクラスを定義する。このクラスはJSONにシリアライズされ、APIクライアントに返される。以下のコードを Application.ktファイルの末尾に以下のコードを追加します:

@Serializable
data class VerifyCodeResponse(val status: String)

すべての修正後 Application.ktファイルはこのようになるはずだ:

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)

APIを使用する

APIの実装は完了したので、テストしてみよう。

デスクトップやモバイルクライアントを含め、どのクライアントでもAPIを使用できますが、ウェブブラウザを使って簡単なテストを行います。

Ktorアプリケーションを起動します。

を実際の電話番号に置き換えてください。 PHONE_NUMBERを実際の電話番号に置き換えて、以下のURLをブラウザで開く:

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

Vonageの電話番号は E.164形式で、「+」と「-」は無効です。電話番号を入力する際は、必ず国コードを指定してください:14155550100 および UK:447700900001

トライアルユーザーとしてSMSの送信と音声通話の発信は、登録した番号と、その他最大4つのテスト番号に対してのみ可能です(この制限を解除するには、Vonageアカウントをトップアップしてください)。

コードが記載されたSMSが届き、同様の応答が表示されるはずです:

{"requestId":"9ac76db7971b4ea4a49f2e061432c6fe"}

2つ目のリクエストを作成する。サーバーから返された値 REQUEST_IDをサーバーから返された値(上の例では 9ac76db7971b4ea4a49f2e061432c6feに置き換え CODEを受け取った検証コードに置き換える:

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

クライアントの電話番号が確認されると、次のような応答が表示されます:

{"status":"OK"}

デフォルトのVonage検証ワークフロー(/verify/verify-v1/guides/workflows-and-events)を使用しているため、125秒以内にコードを入力しないと、コードを読み上げる音声通話がかかってきます。

さらに読む

このチュートリアルで示したコードは Github.

以下は、私たちのサービスをGoで使用するために書かれた他のチュートリアルです:

コミュニティで共有したい質問、アドバイス、アイデアなどがありましたら、お気軽に私たちの コミュニティSlackワークスペース.このチュートリアルを実施した方、あなたのプロジェクトがどのように機能しているか、ぜひお返事ください。

シェア:

https://a.storyblok.com/f/270183/384x384/8ae5af43bb/igor-wojda.png
Igor Wojdaヴォネージの卒業生