Evgenii Legotckoi
Evgenii Legotckoi11 вересня 2023 р. 16:47

Django - захищений медіаконтент

На одному з ресурсів, розробкою яких я займаюся, виникла необхідність додавання захищеного доступу до медіа-контенту, з перевіркою права користувача на доступ до цього медіа-контенту. Простіше кажучи, чи може користувач подивитися фотографію, яку віддає nginx як статичний контент.

Насправді, у випадку nginx і django все набагато простіше, ніж здається на перший погляд.

Давайте розберемося з цим на прикладі завантаження та віддачі фотографії.

Алгоритм роботи з фотографією

  • Завантаження фотографії в спеціальну захищену директорію
  • Спроба отримати фотографію по прямому url до фотографії або на сторінці сайту (наприклад тег img)
  • Перевірка прав доступу до фотографії
  • Створення редиректу в nginx у внутрішню захищену директорію на сервері
  • Отримання фотографії

Здається, все просто, а тепер подивимося, що для цього потрібно.

settings.py

Давайте налаштуємо файл конфігурації django сайту. Зазвичай для завантаження медіа контенту та його автоматичної віддачі через nginx у налаштуваннях пишуть щось подібне.

MEDIA_ROOT = BASE_DIR.parent / 'media'
MEDIA_URL = '/media/'

Однак цього разу ми використовуємо спеціальну захищену директорію, яку я завжди називаю protected .

MEDIA_ROOT = BASE_DIR.parent / 'protected'
MEDIA_URL = '/media/'

Таким чином, ми кажемо, що у зовнішньому просторі це у нас "media" , а в темних чертогах сервера це "protected" .

Такий код може відповідати наступній структурі директорій.

  • /home/www/django_project_root_folder - це основна директорія, де знаходиться проект, статичні та медні каталоги, а також віртуальне оточення
  • /home/www/django_project_root_folder/protected - захищений медіакаталог
  • /home/www/django_project_root_folder/static - директорія зі статичним контентом
  • /home/www/django_project_root_folder/django_project - ваша програма django
  • /home/www/django_project_root_folder/python_venv - віртуальне оточення

Мені зараз подобається саме така структура, оскільки у разі зміни версії python, тобто оновлення віртуального оточення, можна просто ініціалізувати нове оточення і поміняти пару рядків у конфігурації, щоб додаток сайту запускався вже з нового віртаульного оточення. При цьому не постраждає ні репозиторій проекту, ні статичні файли, ні медіа-контент.

Конфігурація nginx

Наведу маленький шмат конфігурації, яка відповідає безпосередньо за налаштування доступу nginx до захищеної директорії

 server {

     # Other code

     location /protected/ {
        internal;
        root /home/www/django_project_root_folder/;
        expires 30d;
    }
}

Цим кодом ми просто вказуємо, де знаходиться каталог protected і що він є внутрішнім, тобто просто так, без спеціального допуску, користувач не отримає його вміст.

Завантаження фотографій

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

# -*- coding: utf-8 -*-

import os
import uuid

from django.contrib.gis.db import models
from django.db.models.signals import post_delete
from django.dispatch import receiver
from django.utils.translation import gettext_lazy as _

from photo.fields import WEBPField
from photo.managers import PhotoManager


def image_folder(instance, filename):
    return 'photos/{}.webp'.format(uuid.uuid4().hex)


class Photo(models.Model):
    class Meta:
        verbose_name = _('Photo')
        verbose_name_plural = _('Photos')

    created_at = models.DateTimeField(verbose_name=_('Created at'), auto_now_add=True)
    updated_at = models.DateTimeField(verbose_name=_('Updated at'), auto_now=True)
    height = models.IntegerField(verbose_name=_('Height'), default=0, blank=True, null=True)
    width = models.IntegerField(verbose_name=_('Width'), default=0, blank=True, null=True)
    image = WEBPField(
        verbose_name=_('Image'),
        upload_to=image_folder,
        height_field='height',
        width_field='width',
    )

    def filename(self):
        return os.path.basename(self.image.name)


@receiver(post_delete, sender=Photo)
def auto_delete_image_on_delete(sender, instance, **kwargs):
    if instance.image:
        if os.path.isfile(instance.image.path):
            os.remove(instance.image.path)

Тут використовується спеціальне поле WEBPField , про яке я вже розповідав. Це поле, яке літом конвертує зображення у формат webp. Також тут є код для автоматичного видалення фотографій із сервера. Але не це найголовніше.

Найголовнішим є ця функція

def image_folder(instance, filename):
    return 'photos/{}.webp'.format(uuid.uuid4().hex)

Ця функція генерує ім'я файлу і вказується куди зберегти файл на сервері щодо protected каталогу.

У результаті фотографія буде збережена наступним шляхом

/home/www/django_project_root_folder/protected/photos/0aec484a6ff246d7ad5eb1b06c0a698e.webp

При цьому в шаблоні код тега img виглядатиме таким чином

<img src="{{ photo.image.url }}"/>

А результат виглядатиме так

<img src="/media/photos/0aec484a6ff246d7ad5eb1b06c0a698e.webp"/>

urls.py

Я почну з вказівки диспетчера URL для отримання доступу до контенту

# -*- coding: utf-8 -*-

from django.urls import path

from photo.views import photo_access

urlpatterns = [
    path('media/photos/<str:path>', photo_access, name='photo'),
]

Як бачите, тут вказано url, який відповідає результуючому для тега img.

Функція photo_access

А тепер найцікавіше, як саме здійснюється доступ до захищеного контенту

# -*- coding: utf-8 -*-

from django.http import HttpResponse, HttpResponseForbidden

from photo.models import Photo
from utils.shortcuts import get_object_or_none


def photo_access(request, path):
    def create_x_accel_redirect(path):
        response = HttpResponse()
        # Content-type will be detected by nginx
        del response['Content-Type']
        response['X-Accel-Redirect'] = '/protected/photos/' + path
        return response

    photo = get_object_or_none(Photo, image='photos/' + path)
    if photo is None:
        return HttpResponseForbidden('Not authorized to access this media.')

    # Some another check code
    if condition is True:
        return create_x_accel_redirect(path)

    if request.user.is_authenticated and request.user.is_staff:
        return create_x_accel_redirect(path)

    return HttpResponseForbidden('Not authorized to access this media.')

У цій функції ми перевіряємо, чи користувач авторизований представник адміністрації ресурсу і чи існує об'єкт фотографії. А також можна додати будь-які інші умови.

Якщо вони виконуються, то за допомогою функції create_x_accel_redirect ми додаємо директиву, яка робить редирект запиту всередині nginx на зображення.

Тобто ми замінюємо /media/photos/0aec484a6ff246d7ad5eb1b06c0a698e.webp на /protected/photos/0aec484a6ff246d7ad5eb1b06c0a698e.webp та перенаправляємо**.
Таким чином, nginx віддає контент із захищеної директорії по media url.

Докладніше з директивою X-Accel-Redirect ви можете ознайомитись на офіційній wiki nginx

Рекомендуємо хостинг TIMEWEB
Рекомендуємо хостинг TIMEWEB
Стабільний хостинг, на якому розміщується соціальна мережа EVILEG. Для проектів на Django радимо VDS хостинг.

Вам це подобається? Поділіться в соціальних мережах!

Коментарі

Only authorized users can post comments.
Please, Log in or Sign up
d
  • dsfs
  • 26 квітня 2024 р. 14:56

C++ - Тест 004. Указатели, Массивы и Циклы

  • Результат:80бали,
  • Рейтинг балів4
d
  • dsfs
  • 26 квітня 2024 р. 14:45

C++ - Тест 002. Константы

  • Результат:50бали,
  • Рейтинг балів-4
d
  • dsfs
  • 26 квітня 2024 р. 14:35

C++ - Тест 001. Первая программа и типы данных

  • Результат:73бали,
  • Рейтинг балів1
Останні коментарі
k
kmssr09 лютого 2024 р. 05:43
Qt Linux - Урок 001. Автозапуск програми Qt під Linux как сделать автозапуск для флэтпака, который не даёт создавать файлы в ~/.config - вот это вопрос ))
АК
Анатолий Кононенко05 лютого 2024 р. 12:50
Qt WinAPI - Урок 007. Робота з ICMP Ping в Qt Без строки #include <QRegularExpressionValidator> в заголовочном файле не работает валидатор.
EVA
EVA25 грудня 2023 р. 21:30
Boost - статичне зв&#39;язування в проекті CMake під Windows Ошибка LNK1104 часто возникает, когда компоновщик не может найти или открыть файл библиотеки. В вашем случае, это файл libboost_locale-vc142-mt-gd-x64-1_74.lib из библиотеки Boost для C+…
J
JonnyJo25 грудня 2023 р. 19:38
Boost - статичне зв&#39;язування в проекті CMake під Windows Сделал всё по-как у вас, но выдаёт ошибку [build] LINK : fatal error LNK1104: не удается открыть файл "libboost_locale-vc142-mt-gd-x64-1_74.lib" Хоть убей, не могу понять в чём дел…
G
Gvozdik19 грудня 2023 р. 08:01
Qt/C++ - Урок 056. Підключення бібліотеки Boost в Qt для компіляторів MinGW і MSVC Для решения твой проблемы добавь в файл .pro строчку "LIBS += -lws2_32" она решит проблему , лично мне помогло.
Тепер обговоріть на форумі
Evgenii Legotckoi
Evgenii Legotckoi03 травня 2024 р. 00:07
Мобильное приложение на C++Qt и бэкенд к нему на Django Rest Framework Добрый день. По моему мнению - да, но то, что будет касаться вызовов к функционалу Андроида, может создать огромные трудности.
IscanderChe
IscanderChe30 квітня 2024 р. 14:22
Во Flask рендер шаблона не передаётся в браузер Доброе утро! Имеется вот такой шаблон: <!doctype html><html> <head> <title>{{ title }}</title> <link rel="stylesheet" href="{{ url_…
G
Gar22 квітня 2024 р. 15:46
Clipboard Как скопировать окно целиком в clipb?
Павел Дорофеев
Павел Дорофеев14 квітня 2024 р. 12:35
QTableWidget с 2 заголовками Вот тут есть кастомный QTableView с многорядностью проект поддерживается, обращайтесь
f
fastrex04 квітня 2024 р. 14:47
Вернуть старое поведение QComboBox, не менять индекс при resetModel Добрый день! У нас много проектов в которых используется QComboBox, в версии 5.5.1, когда модель испускает сигнал resetModel, currentIndex не менялся. В версии 5.15 при resetModel происходит try…

Слідкуйте за нами в соціальних мережах