Новое:Talanto осенью: поиск с цифрами, вакансия по шагам и каталог компаний

+1 512новых вакансий за сутки·54 396вакансий открыто сейчас·Новое:Talanto осенью: поиск с цифрами, вакансия по шагам и каталог компаний

Новые вакансии в TelegramРаботодателям
Talanto
Вакансии
Компании5 565
РезюмеAIПодготовкаСтатьи
BoostТарифыBoost
Войти

Не нашли подходящее?

Пусть нужная вакансия найдёт вас сама.

  1. Подборка в TelegramНовые вакансии по вашему фильтру — сразу в бота.Подключить
  2. Мэтчи по резюмеЗагрузите резюме — покажем, где вы подходите лучше всего.Загрузить
  3. Сохранить фильтрНастройте поиск один раз и возвращайтесь к новым вакансиям.Настроить
Что нового на TalantoTalanto осенью: поиск с цифрами, вакансия по шагам и каталог компаний

Для работодателей

Опубликуйте вакансию — отклики придут в ваш кабинет.

  1. Вакансиябесплатно
  2. Откликиконтакты бесплатно
  3. Наймэтапы и интервью

40 компаний публикуют вакансии напрямую · 4 395 кандидатов открыты к предложениям

Разместить вакансию

Популярные направления

Все подборки
  • Python8 752
  • Облачные технологии6 631
  • QA вакансии2 183
  • Android1 322
  • Инженер данных860
  • Продакт-менеджер316

Разработка

  • iOS1 238
  • Backend вакансии1 196
  • AI вакансии549
  • Fullstack вакансии500
  • Frontend вакансии426
  • Mobile вакансии28

По стеку

  • Go5 209
  • Java3 131
  • Kotlin1 113
  • Node.js1 008
  • Rust833
  • .NET656
  • PHP322
  • C++270
  • C#207

DevOps и инфраструктура

  • Kubernetes3 882
  • Linux-инфраструктура2 817
  • DevOps вакансии2 655
  • Terraform / IaC2 191
  • Мониторинг и Observability1 071
  • CI/CD919
  • SRE308
  • Кибербезопасность38

Аналитика и данные

  • Аналитик данных522
  • Data Scientist435
  • Бизнес-аналитик397
  • Системный аналитик67

Другие направления

  • Проджект-менеджер109
  • UI/UX дизайн67
  • Ручное тестирование14

По регионам

  • Вакансии в США22 951
  • Вакансии в Европе10 259
  • Работа в России2 963
  • Вакансии в Канаде2 034

По формату

  • Удалённые вакансии39 691
  • Релокация в США22 951
  • Гибридная работа3 633
  • Офисная работа2 761

По уровню

  • Senior вакансии19 599
  • Middle вакансии11 130
  • Junior вакансии3 562

Сейчас нанимают

Все компании
  • Wise192 вакансии
  • Rakuten189 вакансий
  • ASML163 вакансии
  • Grab134 вакансии
  • Celonis42 вакансии
  • Spotify37 вакансий
  • Adyen28 вакансий
  • Infobip28 вакансий
  • Monzo20 вакансий
  • Darktrace17 вакансий

Вакансии из 344 источников: карьерные страницы компаний и джоб-борды.

КомпанииО насНовостиПубличная офертаУсловия использованияПолитика конфиденциальностиПоддержка
←К теме KtorВсе вопросы
KtorMiddleТеория

Какие security-риски возникают в Ktor при CORS, JWT/session auth и обработке multipart-загрузок?

В закладки
Сначала попробуйте ответить сами30 секунд
Сравнить с ответом ↓

Основные риски: CORS с anyHost() открывает API любому домену; JWT без проверки iss/aud позволяет использовать токены с других сервисов; multipart без ограничения размера и типа файла уязвим к DoS и path traversal.

CORS: неправильная конфигурация

CORS-плагин Ktor по умолчанию блокирует все cross-origin запросы. Проблемы начинаются при излишне мягкой настройке:

// ОПАСНО: разрешает любой origin
install(CORS) {
    anyHost()  // эквивалент Access-Control-Allow-Origin: *
    allowCredentials = true  // НЕЛЬЗЯ вместе с anyHost()
    allowHeader(HttpHeaders.Authorization)  // лишнее при cookie-auth
}

// ПРАВИЛЬНО: явный список origins
install(CORS) {
    allowHost("app.example.com", schemes = listOf("https"))
    allowHost("admin.example.com", schemes = listOf("https"))
    allowCredentials = true
    allowHeader(HttpHeaders.ContentType)
    allowMethod(HttpMethod.Put)
    allowMethod(HttpMethod.Delete)
    maxAgeInSeconds = 3600
}

Важно: allowCredentials = true и anyHost() несовместимы по спецификации — браузер откажет в таком запросе, но это не защита, т.к. атака может идти не из браузера.

Security-риски в Ktor: CORS, JWT/sessions и multipart

JWT: неполная верификация

// ОПАСНО: не проверяет iss и aud
install(Authentication) {
    jwt("auth-jwt") {
        verifier(JWT.require(Algorithm.HMAC256(secret)).build())
        validate { it.payload.getClaim("sub").asString()?.let { JWTPrincipal(it.payload) } }
    }
}

// ПРАВИЛЬНО: строгая верификация
install(Authentication) {
    jwt("auth-jwt") {
        realm = "My API"
        verifier(
            JWT.require(Algorithm.HMAC256(secret))
                .withIssuer("https://auth.example.com")   // проверяем источник
                .withAudience("my-api")                    // проверяем получателя
                .acceptLeeway(30)                          // 30 сек погрешность часов
                .build()
        )
        validate { credential ->
            val sub = credential.payload.getClaim("sub").asString()
            val exp = credential.payload.expiresAt
            if (sub != null && exp != null && exp.after(Date())) {
                JWTPrincipal(credential.payload)
            } else null
        }
        challenge { _, _ ->
            call.respond(HttpStatusCode.Unauthorized, mapOf("error" to "Invalid token"))
        }
    }
}

Session auth: защита кук

install(Sessions) {
    cookie<UserSession>("session") {
        cookie.httpOnly = true        // недоступна из JavaScript
        cookie.secure = true          // только HTTPS
        cookie.sameSite = SameSite.Strict  // защита от CSRF
        cookie.maxAgeInSeconds = 3600
        // Шифрование + подпись куки
        transform(SessionTransportTransformerEncrypt(
            hex("00112233445566778899aabbccddeeff"),  // encryptionKey 16/24/32 bytes
            hex("02030405060708090a0b0c0d0e0f1011")   // signKey 32 bytes
        ))
    }
}

Multipart: DoS и path traversal

// ОПАСНО: нет ограничений на размер и тип
post("/upload") {
    val multipart = call.receiveMultipart()
    multipart.forEachPart { part ->
        if (part is PartData.FileItem) {
            val file = File("uploads/${part.originalFileName}")
            part.streamProvider().use { it.copyTo(file.outputStream()) }
        }
        part.dispose()
    }
}

// ПРАВИЛЬНО: ограничения размера, типа и имени файла
post("/upload") {
    // Ограничение размера всего запроса (16 MB)
    val multipart = call.receiveMultipart(formFieldLimit = 16 * 1024 * 1024L)

    multipart.forEachPart { part ->
        if (part is PartData.FileItem) {
            // Проверяем Content-Type
            val contentType = part.contentType?.toString() ?: ""
            require(contentType in listOf("image/jpeg", "image/png", "application/pdf")) {
                "Unsupported file type"
            }

            // Защита от path traversal: только имя файла без пути
            val safeName = File(part.originalFileName ?: "file").name
                .filter { it.isLetterOrDigit() || it == '.' || it == '_' || it == '-' }
            require(safeName.isNotBlank()) { "Invalid file name" }

            // Сохраняем в UUID-именованный файл
            val dest = File("uploads/${UUID.randomUUID()}_${safeName}")
            part.streamProvider().use { input ->
                dest.outputStream().use { output ->
                    input.copyTo(output, bufferSize = 8192)
                }
            }
        }
        part.dispose()
    }
    call.respond(HttpStatusCode.Created)
}

Подводные камни

  • anyHost() + allowCredentials = true: браузер отклонит ответ, но это не защита от non-browser клиентов.
  • JWT exp проверяется библиотекой java-jwt, но только если вы вызываете .build() без явного acceptExpiresAt — без этого просроченные токены принимаются.
  • Имя файла из multipart контролируется клиентом — ../../../etc/passwd в originalFileName — всегда санировать.
  • Session cookie без SameSite=Strict уязвима к CSRF-атакам даже с httpOnly=true.
  • ContentType multipart/form-data не ограничивает размер отдельного поля — используйте formFieldLimit в receiveMultipart().
  • Загруженные файлы нужно проверять magic bytes, а не только Content-Type — клиент может отправить исполняемый файл с image/jpeg.
  • CORS не защищает от запросов с curl или мобильных приложений — это защита только для браузеров; для API нужна полноценная авторизация.
  • Refresh token без rotation позволяет переиспользовать украденный токен бесконечно — инвалидируйте старый при выдаче нового.

Частые вопросы на собеседовании

  • Каковы варианты production-деплоя приложения Ktor?
  • Как определять маршруты (routes) в Ktor и как работает вложенная маршрутизация?
  • Что такое plugins (ранее features) в Ktor и как их устанавливать?
  • Что такое Ktor и каковы его ключевые преимущества перед другими Kotlin-фреймворками?

Типичные ошибки

  • Отвечать определением без production-сценария.
  • Не называть runtime boundary, security boundary или failure mode.
  • Игнорировать версию API, observability и тестовую проверку.

Что проверяет интервьюер

  • Объясняет механизм своими словами и без выдуманных API.
  • Называет реальные риски, диагностику и критерий корректности.
  • Связывает ответ с текущей документацией и миграционными ограничениями.

Как вы знаете этот вопрос?

Проверить себя по теме Ktor →

Источники

  • https://ktor.io/docs/server-deployment.html
Следующий вопрос→Каковы варианты production-деплоя приложения Ktor?

Связанные темы

Ktorktorktor-productionrisk-analysistechnical-depth