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
AD

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

  • Результат:50бали,
  • Рейтинг балів-4
m
  • molni99
  • 26 жовтня 2024 р. 01:37

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

  • Результат:80бали,
  • Рейтинг балів4
m
  • molni99
  • 26 жовтня 2024 р. 01:29

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

  • Результат:20бали,
  • Рейтинг балів-10
Останні коментарі
ИМ
Игорь Максимов22 листопада 2024 р. 11:51
Django - Підручник 017. Налаштуйте сторінку входу до Django Добрый вечер Евгений! Я сделал себе авторизацию аналогичную вашей, все работает, кроме возврата к предидущей странице. Редеректит всегда на главную, хотя в логах сервера вижу запросы на правильн…
Evgenii Legotckoi
Evgenii Legotckoi31 жовтня 2024 р. 14:37
Django - Урок 064. Як написати розширення для Python Markdown Добрый день. Да, можно. Либо через такие же плагины, либо с постобработкой через python библиотеку Beautiful Soup
A
ALO1ZE19 жовтня 2024 р. 08:19
Читалка файлів fb3 на Qt Creator Подскажите как это запустить? Я не шарю в программировании и кодинге. Скачал и установаил Qt, но куча ошибок выдается и не запустить. А очень надо fb3 переконвертировать в html
ИМ
Игорь Максимов05 жовтня 2024 р. 07:51
Django - Урок 064. Як написати розширення для Python Markdown Приветствую Евгений! У меня вопрос. Можно ли вставлять свои классы в разметку редактора markdown? Допустим имея стандартную разметку: <ul> <li></li> <li></l…
d
dblas505 липня 2024 р. 11:02
QML - Урок 016. База даних SQLite та робота з нею в QML Qt Здравствуйте, возникает такая проблема (я новичок): ApplicationWindow неизвестный элемент. (М300) для TextField и Button аналогично. Могу предположить, что из-за более новой верси…
Тепер обговоріть на форумі
Evgenii Legotckoi
Evgenii Legotckoi24 червня 2024 р. 15:11
добавить qlineseries в функции Я тут. Работы оень много. Отправил его в бан.
t
tonypeachey115 листопада 2024 р. 06:04
google domain [url=https://google.com/]domain[/url] domain [http://www.example.com link title]
NSProject
NSProject04 червня 2022 р. 03:49
Всё ещё разбираюсь с кешем. В следствии прочтения данной статьи. Я принял для себя решение сделать кеширование свойств менеджера модели LikeDislike. И так как установка evileg_core для меня не была возможна, ибо он писался…
9
9Anonim25 жовтня 2024 р. 09:10
Машина тьюринга // Начальное состояние 0 0, ,<,1 // Переход в состояние 1 при пустом символе 0,0,>,0 // Остаемся в состоянии 0, двигаясь вправо при встрече 0 0,1,>…

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