Проектирование структуры аутентификации для видеостриминга

Проектирование структуры аутентификации для видеостриминга

-Пример создания сервиса видеоинструкций на основе ReactPlayer-

1. Обзор проекта

Данный случай является проектом, выполненным для перевода существующей системы пользовательских руководств на основе PDF в сервис, основанный на видео-контенте. В существующей системе пользователи нажимали на кнопку руководства, и Frontend напрямую получал доступ к AWS S3 для скачивания PDF-файлов. Этот подход был простым в реализации, но имел ограничения, такие как трудности, связанные с одновременной проверкой рабочего экрана и руководства, а также сложностью интуитивной передачи сложных процедур.

Клиент потребовал, чтобы пользователи могли одновременно просматривать руководство в формате видео, сохраняя при этом рабочий экран, с использованием функции PIP (Картина в картине). В то же время в соответствии с политикой безопасности прямой доступ к AWS S3 должен был быть заблокирован, и существовали ограничения на возможность включения чувствительной информации для аутентификации в URL-параметры. Таким образом, ключевым моментом этого проекта стало не просто внедрение функции воспроизведения видео, а проектирование структуры аутентификации и потоковой передачи, которая удовлетворяет требованиям безопасности и использует браузерную технологию воспроизведения видео.

2. Выбор технологий и направление проектирования

В области фронтенда была введена библиотека видеоплеера на базе React под названием ReactPlayer. ReactPlayer работает на основе тега HTML5 video, обладает высокой совместимостью с окружением React и имеет преимущества в виде простоты управления функцией PIP и состоянием воспроизведения.

Однако ReactPlayer использует способ запроса тега video, поэтому запросы виде файлов изначально выполняются по методу GET. Поскольку не удается включить учетные данные в тело запроса или свободно настраивать заголовки, потребовалось разделить процесс аутентификации и фактический запрос на воспроизведение видео.

Пример кода 1. Пример вызова ReactPlayer

<ReactPlayer
  url={videoEndpoint}
  controls
  pip
  playing={false}
  width="100%"
  height="100%"
/>

Этот пример иллюстрирует структуру, в которой ReactPlayer воспроизводит видео на основе конечной точки воспроизведения видео, полученной от сервера. Учетные данные не включаются в URL, а передаются в виде временного токена в Cookie, выданного на этапе предварительной аутентификации.

3. Переработка архитектуры

Существующая структура позволяла Frontend напрямую обращаться к AWS S3. Однако при предоставлении видеоконтента сохранение той же структуры могло привести к утечке путей хранения, повторному использованию URL и возможности обхода контроля доступа. В связи с этим были разделены роли серверов следующим образом.

Frontend
  -> Server A (사용자 인증 및 스트리밍 요청 중계)
  -> Server B (파일 조회 전용 서버)
  -> AWS S3

Сервер A отвечает за аутентификацию пользователей, проверку прав, выдачу временных токенов и промежуточную передачу запросов на транслирование. Сервер B был отделен, чтобы отвечать только за получение фактических видеофайлов и доступ к AWS S3. Это блокирует возможность клиенту узнать путь хранения и обеспечивает прохождение всех запросов на видео через проверку аутентификации сервера.

4. Аутентификация и поток воспроизведения

Когда пользователь нажимает кнопку меню, сначала отправляется запрос API для проверки пользователя на Server A, не вызывая видеоплеер сразу. После завершения проверки Server A выдает конечную точку для воспроизведения видео и временный токен с ограниченным сроком действия. Временный токен сохраняется в Cookie браузера, а затем передается вместе с вызовом конечной точки видео при вызове ReactPlayer.

Пример кода 2. Запрос информации о воспроизведении видео

const openManualVideo = async (manualId: string) => {
  const response = await api.get(`/api/videos/${manualId}/authorize`);
  setVideoEndpoint(response.data.videoEndpoint);
  setManualModalOpen(true);
};

В этом коде показан пример запроса к конечной точке воспроизведения видео при нажатии на кнопку руководства. На этом этапе сервер проверяет права пользователя и отвечает, сохраняя временный токен в Cookie.

Пример кода 3. Пример выпуска временного токена Cookie

ResponseCookie cookie = ResponseCookie.from("VIDEO_ACCESS_TOKEN", token)
    .httpOnly(true)
    .secure(true)
    .path("/api/videos/")
    .maxAge(Duration.ofMinutes(30))
    .sameSite("Strict")
    .build();

return ResponseEntity.ok()
    .header(HttpHeaders.SET_COOKIE, cookie.toString())
    .body(new VideoAuthorizeResponse(videoEndpoint));

Cookie настроены с параметрами HttpOnly, Secure, SameSite, чтобы ограничить доступ к скриптам и ненужную передачу данных за пределы. Кроме того, path и maxAge были указаны, чтобы использовать их только для ограниченных запросов на воспроизведение видео.

5. Обработка потоковой трансляции

Потоковая трансляция видео, в отличие от обычного скачивания файлов, подразумевает непрерывную передачу больших объемов данных, и когда пользователь перемещает позицию воспроизведения, могут возникать частичные запросы на основе заголовка Range. Поэтому Сервер A должен передать заголовок Range клиента на Сервер B, а Сервер B должен вернуть статус ответа и основные заголовки обратно клиенту.

Образец кода 4. Потоковая передача на основе заголовка Range

@GetMapping("/api/videos/{manualId}/stream")
public Mono>> stream(
    @PathVariable String manualId,
    @RequestHeader(value = HttpHeaders.RANGE, required = false) String range
) {
  return webClient.get()
      .uri("/internal/files/{manualId}", manualId)
      .headers(headers -> {
        if (range != null) headers.set(HttpHeaders.RANGE, range);
      })
      .exchangeToMono(response -> {
        Flux body = response.bodyToFlux(DataBuffer.class);
        ResponseEntity.BodyBuilder builder = ResponseEntity.status(response.statusCode());
        response.headers().asHttpHeaders().forEach((name, values) -> {
          if (isStreamingHeader(name)) builder.header(name, values.toArray(String[]::new));
        });
        return Mono.just(builder.body(body));
      });
}

Ключевым моментом этого подхода является то, что он не загружает весь файл в память, как это происходит с bodyToMono(Resource.class) или bodyToMono(byte[].class). Используя bodyToFlux(DataBuffer.class), можно передавать видео данные по частям (chunk), что делает его подходящим для обработки больших объемов видео.

Пример кода 5. Основная фильтрация заголовков потоковой передачи

private boolean isStreamingHeader(String name) {
  return HttpHeaders.CONTENT_TYPE.equalsIgnoreCase(name)
      || HttpHeaders.CONTENT_LENGTH.equalsIgnoreCase(name)
      || HttpHeaders.CONTENT_RANGE.equalsIgnoreCase(name)
      || HttpHeaders.ACCEPT_RANGES.equalsIgnoreCase(name)
      || HttpHeaders.CACHE_CONTROL.equalsIgnoreCase(name);
}

Заголовки, такие как Content-Range, Accept-Ranges, Content-Length, необходимы браузеру для определения длины видео и диапазона частичных ответов. Особенно важно сохранить эти заголовки для функции навигации и надежного воспроизведения.

6. Результаты и эффекты

Результаты реализации позволили пользователям сразу воспроизводить видео в браузере и одновременно использовать экран рабочего процесса и видеоинструкцию через функцию PIP. Кроме того, удалив прямой доступ к AWS S3 и внедрив управление доступом на серверной основе, мы смогли удовлетворить требования безопасности.

• Устранение прямой экспозиции AWS S3

• Устранение передачи конфиденциальной информации на основе URL

• Разрешение доступа к видео endpoint после верификации пользователя

• Ограниченный контроль доступа на основе временного токена

• Поддержка воспроизведения и навигации видео на основе заголовка диапазона

7. Заключение

В данном случае мы не просто добавили видеоплеер, но и получили опыт проектирования, учитывая способы работы ReactPlayer и HTML5 video тегов, политику безопасности клиента, разделение ролей между серверами и способы потоковой обработки. Разделив процессы аутентификации и воспроизведения, а также четко разграничив ответственность между Сервером A и Сервером B, мы смогли одновременно обеспечить пользователям хороший опыт и безопасность.

Особенно в практической среде я почувствовал, что важно понимать ограничения не только реализации функционала, но и политики безопасности и поведения браузеров. В ходе данного проекта я подтвердил, что для обеспечения надежного предоставления услуг в видеобазированных сервисах необходимо учитывать структуру аутентификации, поток данных, обработку потоковых заголовков и оперативную политику безопасности.

НЗ

Site footer