У меня есть приложение REST API на основе Ktor, которое использует токен jwt
в качестве аутентификации. Тогда я должен ограничить определенные маршруты для конкретной роли. Для этого я создаю принципал, содержащий соответствующую информацию:
data class UserPrincipal (
val id: Long,
val username: String,
val roleId: Long,
): Princpal {
override fun getName() = username
}
object AuthLogin {
fun Application.auth(jwt: JwtProvider) {
install(Authentication) {
jwt("jwt") {
realm = jwt.realm()
verifier(jwt.verifier())
validate {
val userId = it.payload.getClaim("id").asLong()
val username = it.payload.getClain("name")
val roleId = it.payload.getClaim("roleId").asLong()
UserPrincipal(userId, username, roleId)
}
}
}
}
}
Претензии с userId
и roleId
предоставляются при подписании правильно вошедшего в систему пользователя. Теперь я могу ограничить конечные точки REST следующим образом:
object RestModule {
fun Application.enititiesOne(userRepo: UserRepo) {
routing {
authenticate("jwt") {
route("/entities1") {
get {
val principal = call.principal<UserPrincipal>()
when(userRepo.hasAccessByRole(principal!!.roleId, "CAN_R_E1") {
false -> call.respond(HttpStatusCode.Forbidden)
true -> // some retrieval logic
}
post {
val principal = call.principal<UserPrincipal>()
when(userRepo.hasAccessByRole(principal!!.roleId, "CAN_W_E1") {
false -> call.respond(HttpStatusCode.Forbidden)
true -> // some update logic
}
}
}
}
}
}
Как вы можете видеть даже внутри одной функции маршрутизации, мне приходится дублировать код, который дважды проверяет роль руководителя. Я могу перевести его в рабочее состояние, но мне нужно единственное место для определения моих ролей безопасности. Примерно так:
authenticate {
val principal = call.principal<UserPrincipal()
val rights = userRepo.rightsByRole(principal.roleId)
when(routes) {
get("/entities1/**") ->
if(rights.contain("CAN_R_E1")) call.proceed
else call.respond(HttpStatusCode.Forbidden)
post("/entites1) -> rights.contain("CAN_W_E1") // similar
get("/entities2/**") -> rights.contain("CAN_R_E2") // similar
else -> call.respond(401)
}
}
А затем подключите его к остальным конечным точкам. Или есть какой-то похожий подход, который я могу использовать в Ktor Kotlin? Кажется, мне нужны перехватчики, но я не уверен, как использовать их по назначению.