LoginSignup
1
1

More than 1 year has passed since last update.

KtorでMock Authentication(モック認証)

Posted at

認証を モック化したい ことがある

KtorアプリケーションのRoute上のAPIをテストしたい場合、ログイン等の認証が必須のものについては、何らかの方法で認証を突破する必要があります。
認証方式によって突破方法はさまざまですが、今回はKtorのAuthenticationプラグイン(Feature)を拡張実装するアプローチをご紹介します。

代表的な恩恵は、HTTPリクエストヘッダーのAuthorization属性にモックの情報の差し込む、などの小細工が必要なくなることでしょうか。さっそく見ていきましょう。

1. モック認証用の拡張関数の作成

KtorのAuthenticationプラグインにデフォルトで実装されているformbasicといった関数を参考に、セットした認証主体の情報を返すだけの拡張関数を作成します。
実装すべきは大きく2つ、①Authentication.Configurationの拡張関数と、②拡張関数中で利用するAuthenticationProviderの継承クラスです。

// io.ktor.auth.Authenticationを拡張
fun Authentication.Configuration.mock(
    name: String? = null,
    configure: MockAuthenticationProvider.Configuration.() -> Unit
) {
    val provider = MockAuthenticationProvider.Configuration(name).apply(configure).build()

    // 認証時に実行される処理の定義
    provider.pipeline.intercept(AuthenticationPipeline.RequestAuthentication) { context ->
        // 認証主体の取得関数を実行(関数は別のところで設定)
        val principal = provider.principalProvider(call)

        // 認証主体がnullなら401を返して認証を中断
        if (principal == null) {
            call.respond(UnauthorizedResponse())
            context.challenge.complete()
            return@intercept
        }

        // (認証情報がnullでない場合) 
        // 認証主体をcontextにセット -> call.principalで取得可能に
        context.principal(principal)
    }
    register(provider)
}
// io.ktor.auth.AuthenticationProviderを継承
class MockAuthenticationProvider(config: Configuration) : AuthenticationProvider(config) {
    val principalProvider = config.principalProvider

    // io.ktor.auth.AuthenticationProvider.Configurationを継承
    class Configuration(name: String?) : AuthenticationProvider.Configuration(name) {
        var principalProvider: ApplicationCall.() -> Principal? = { null }
        fun build() = MockAuthenticationProvider(this)

        // 「認証主体の情報をセットする関数」を渡す関数
        fun principal(principalProvider: ApplicationCall.() -> Principal?) {
            this.principalProvider = principalProvider
        }
    }
}

2. モック認証のinstallerの実装

認証プラグインをinstallする拡張関数を実装します。
ここでprincipal{ }関数に渡した情報が、ルーティング上のcall.principal()から取得されます。
利用ごとに認証情報を切り替えたい場合は、関数の引数などから情報を差し込めるようにしておきます。以下、サンプルです。

// io.ktor.application.Application
fun Application.testAuth(loginUser: LoginUser? = null) {
    install(Authentication) {
        // 1.で作成した関数を呼び出し
        mock {
            // ログイン主体(Principal)の取得ロジックを記載
            principal { loginUser }
        }
    }
}

3. アプリケーションへの組み込み

モック認証を利用したいアプリケーション(のモジュール)に、モック認証のinstallerを追加します。
本番運用でモック認証は使わないでしょうから、
1. テスト時と本番利用時の実行モジュールを分けて定義
2. それぞれ別の認証installerを追加
とした上、テスト時には認証をモック化したモジュールを利用するようにします。

// テスト時に実行するモジュール
fun Application.testModule(loginUser: LoginUser? = null) {
    testAuth(loginUser)
    others()
}

// 本番利用時に実行するモジュール
fun Application.mainModule() {
    productionAuth() //mockでないauth
    others()
}

// 認証以外のモジュール
fun Application.others() {
    router()
    // ...and other modules
}

fun Application.router() {
    routing {
        authenticate {
            get("/hello") {
                val loginUser = call.principal<LoginUser>()!!
                // ...and some logics    
            }
        }
    }
}

4. 使う

あとは「テスト用のモジュール」の利用場面で、認証主体を指定するだけ。
以下は前掲のモジュールを利用したテストのサンプルです。testModuleの引数に渡した「ログインユーザー(loginUser)」が、そのままルーティング上のcall.principal()から取り出されます。

// Ktorのテストライブラリを利用してApiのテストをするサンプル
class Test {
    @Test
    fun `authenticated hello - success`() {
        // io.ktor.server.testing.withTestApplication
        withTestApplication({ testModule(loginUser = TestLoginUser.someone) }) {
            handleRequest(HttpMethod.Get, "/hello") { }
                .apply {
                    // ...some assertions
                }
        }
    }
}

むすび

モック認証の都合が利用者側に露出しにくい(魔法のパラメータなどを仕込まなくていい)のは気に入っています。
認証をテスト時にモック化したいというニーズはそれなりに普遍的なはずが、情報が少ないのが意外…。ご参考までに。ポン・チャガール1

実装サンプル全体(GitHub)

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1