Ktor: Как проверить аутентификацию внутри обработчика маршрута?

Я использую ktor v0.9.2 и хочу отправлять разный контент по одному и тому же маршруту, в зависимости от того, аутентифицирован пользователь или нет.

У меня проблема в том, что я не могу получить доступ к принципалу за пределами блока authenticate { }.

Моя установка такая:

data class User(
    val userId: Int
) : io.ktor.auth.Principal

fun Application.myApp() {
    install(Authentication) {
        jwt {
            verifier(JwtConfig.verifier)
            validate { credential ->
                val userId = credential.payload.getClaim("userId").asInt()
                when {
                    userId > 0 -> User(userId)
                    else -> null
                }
            }
        }
    }
    install(DefaultHeaders)
    install(CallLogging)
    install(ContentNegotiation) {
        jackson { }
    }
    install(Routing) {
        authenticate {
            get("/protected") {
                // This works fine!!
                val user = call.authentication.principal<User>()
                call.respond(user)
            }
        }

        get("/") {
            val user = call.authentication.principal<User>() // -> User is always null here
            if (user == null) {
                call.respondText("Not Logged User")
            } else {
                call.respondText("Logged User")
            }
        }
    }
}

Маршрут /protected работает нормально, но в маршруте / принципал всегда равен нулю. Я думаю, что это какой-то конвейер, но я немного растерялся. Может ли кто-нибудь дать некоторое представление? Спасибо!


person echoes    schedule 05.06.2018    source источник


Ответы (1)


Какую версию ktor вы используете? Вы можете показать нам свою настройку авторизации?

У вас должно получиться что-то вроде этого (0.9.2):

install(Authentication) {
        jwt {
            verifier(JwtConfig.verifier)
            realm = JwtConfig.realm
            validate {
                val email = it.payload.getClaim("email").toString()
                userRepository.findUser(email)?.let { user ->
                    val token = JwtConfig.makeToken(user)
                    user.copy(token = token)
                }
            }
        }

}

Если процесс аутентификации прошел успешно, пользователь будет доступен через принципала.

Вот обновленный код для 0.9.3. Начнем с теста для проверки поведения. Просто добавьте флаг optional.

class KtorTest {

    @Test fun server() {
        withTestApplication({ myApp() }) {
            val userId = 918354853
            val token = JwtConfig.makeToken(User(userId))
            // The protected route
            handleRequest {
                uri = "/protected"
                addHeader("Authorization", "Bearer $token")
            }.let {
                it.requestHandled shouldBe true
                it.response.content.shouldNotBeNullOrBlank() shouldContain userId.toString()
            }

            // Optional route without token
            handleRequest {}.let {
                it.requestHandled shouldBe true
                it.response.content.shouldNotBeNullOrBlank() shouldBeEqualTo "Not Logged User"
            }

            // Optional route with token
            handleRequest {
                addHeader("Authorization", "Bearer $token")
            }.let {
                it.requestHandled shouldBe true
                it.response.content.shouldNotBeNullOrBlank() shouldBeEqualTo "Logged User"
            }
        }
    }

}

data class User(val userId: Int) : Principal

fun Application.myApp() {
    install(Authentication) {
        jwt {
            verifier(JwtConfig.verifier)
            validate { credential ->
                val userId = credential.payload.getClaim("userId").asInt()
                when {
                    userId > 0 -> User(userId)
                    else -> null
                }
            }
        }
    }
    install(DefaultHeaders)
    install(CallLogging)
    install(ContentNegotiation) { jackson { } }
    install(Routing) {
        authenticate {
            get("/protected") {
                // This works fine!!
                val user = call.authentication.principal<User>()!!
                call.respond(user)
            }
        }

        authenticate(optional = true) {
            get("/") {
                val user = call.authentication.principal<User>() // -> User is always null here
                if (user == null) {
                    call.respondText("Not Logged User")
                } else {
                    call.respondText("Logged User")
                }
            }
        }
    }
}
person avolkmann    schedule 06.06.2018
comment
Извините, я не дал вам достаточно подробностей. Я отредактировал свой вопрос сейчас. - person echoes; 07.06.2018
comment
Итак, в основном вы ищете дополнительную аутентификацию? Я только что связался с командой ktor, чтобы узнать об этой функции, но похоже, что она еще не работает. В любом случае причина того, что ваш второй маршрут не получает никакой аутентификации, заключается в том, что он не заключен в какой-либо блок authenticate. - person avolkmann; 10.06.2018
comment
Я добавил код для новой дополнительной функции аутентификации, доступной в версии 0.9.3. - person avolkmann; 07.08.2018
comment
Спасибо, Андреас! - person echoes; 15.08.2018
comment
не будет ли это создавать новый токен у пользователя каждый раз, когда вы проверяете? - person filthy_wizard; 14.12.2019