Django REST Framework для ссылок на страницы не использует HTTPS

Я настраиваю разбивку на страницы для определенной конечной точки DRF, которая работает хорошо, однако при развертывании на моем сервере, который использует HTTPS, ссылки на следующую и предыдущие страницы формируются с помощью http:// вместо https://. Это заставляет браузер блокировать следующие/предыдущие страницы.

Я дважды проверил, что был отправлен исходный запрос, с HTTPS, а второй ответ на этот вопрос утверждает, что он должен использовать HTTPS в сформированных URL-адресах поскольку запрос пришел через HTTPS.

Первый ответ на тот же вопрос тоже не помог - я добавил строку X-Forwarded-Proto в мою конфигурацию nginx и перезагрузился, но безрезультатно.

В документах DRF упоминается, что reverse() должен вести себя как базовый Django reverse, однако кажется довольно очевидным, что первоначальный запрос является HTTPS while возвращаемый URL-адрес - HTTP.

Вот несколько скриншотов, которые показывают начальный запрос (https://<domain>.com/api/leaderboard/):

введите описание изображения здесь

С ответом, содержащим next: http://<domain>.com/api/leaderboard/?page=2):

введите описание изображения здесь

Я понял, что это будет простая настройка, но не удалось найти что-либо после поиска как этого сайта, так и сайта DRF.

Это моя конфигурация nginx:

 location / {
    # proxy_pass http://127.0.0.1:9900;
    proxy_set_header X-Forwarded-Host $server_name;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-Proto $scheme;
    add_header P3P 'CP="ALL DSP COR PSAa PSDa OUR NOR ONL UNI COM NAV"';

    root /opt/app/client/dist;
    index index.html index.htm;

}

Этот вопрос содержит довольно подробный ответ, но в конечном итоге говорит, что URL-адреса сформированы с тем же протоколом, что и запрос, что, похоже, не имеет места здесь, Нужно ли устанавливать этот Django SECURE_PROXY_SSL_HEADER? Я не был уверен, учитывая предупреждение, что это потенциально небезопасно.

Ответ 1

Нужно ли устанавливать этот Django SECURE_PROXY_SSL_HEADER? Я не был уверен, учитывая предупреждение, что это потенциально небезопасно.

Да, да. Однако вам нужно позаботиться о том, что вы делаете. В частности, убедитесь, что он отключил X-Forwarded-Proto снаружи.

Ответ 2

Так как это первый SO пост, который появляется в результатах поиска Google, я думаю, что было бы полезно поделиться, как я решил это. У меня есть вкус Kubernetes, но под капотом логика не так отличается от других.

Я использую входной контроллер Kubernetes, расположенный перед Django, так что все может отличаться от вас, но я кратко изложу свой контекст, чтобы вы могли увидеть, полезен ли он для вас:

Проблема

Таким образом, у меня возникла та же проблема, что и у OP, ссылка разбиения на страницы из DRF всегда http, даже если я использую конечную точку API с помощью https. Это станет более понятным, если вы настроили API-интерфейс браузера, который поставляется вместе с Django REST Framework (DRF), и перейдете на корневую страницу API. Я вижу, что все ссылки, сгенерированные DRF, являются http независимо от того, какой протокол я использую при посещении сайта.

Мой контекст

Стек по заказу из внешнего мира до Джанго:

  1. Kuberenetes Nginx Ingress Controller (с настройкой SSL с использованием letsencrypt)
  2. Правило входа указывает на сервис Django
  3. Gunicorn запускает сервер Django
  4. Джанго читает settings.py и начинает обслуживать запросы

Как я отлаживал и выяснял причину

  1. Дамп nginx.conf входного контроллера. В случае, если вы не знаете, как это сделать: сначала определите имя модуля с помощью kuberctl get pods -n <namespace of your ingress controller>, запишите это имя, а затем сохраните файл с помощью kubectl exec -it -n ingress_controller_namespace ingress_controller_pod_name cat /etc/nginx/nginx.conf > nginx.conf.
  2. Посмотрите на nginx.conf, проверьте, есть ли у вас proxy_set_header X-Forwarded-Proto $scheme; в разделе location для домена вашего сайта, или похожая вещь, которая правильно устанавливает заголовок прокси X-Forwarded-Proto. По умолчанию входной контроллер Kubernetes nginx должен уже иметь эту строку (или эквивалентную) для вас.
  3. Далее запрос направляется в Gunicorn. Одно замечание: вы захотите добавить --forwarded-allow-ips="*" к вашей команде gunicorn, или, если вы знаете свой IP-адрес сервера nginx, вы можете ограничиться этим, так что gunicorn будет перенаправлять вам заголовки, в противном случае gunicorn будет снимать эти заголовки, Таким образом, вы получаете команду типа gunicorn django_server.wsgi:application --forwarded-allow-ips="*" --workers=${PROPER_WORKER_NUM} --log-level info --bind 0.0.0.0:8001. У вас может быть 4 рабочих, указав workers=4, обычно это зависит от того, какой процессор у вас на сервере. --log-level info только для отладки, он необязательный.
  4. В Django вам нужно иметь SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https') в settings.py, о котором упоминал другой ответ. Вы можете удивиться, почему префикс HTTP_ в то время как в nginx наш заголовок называется X-Forwarded-Proto. Это потому, что WSGI добавит префикс к заголовкам, которые он распознает. Эта строка в основном говорит, что если заголовок HTTP_X_FORWARDED_PROTO равен строке "https", то Django распознает запрос как безопасный. Это повлияет на некоторые виды поведения в Джанго, например, вы получите request.is_secure == True, request.build_absolute_uri(None) == 'https://...' и, самое главное, для нумерации страниц Django REST Framework теперь будет использоваться https! (Пока вы действительно ударили API https)

Хорошо, теперь вы можете проверить снова. Если DRF дает вам https сейчас - поздравляю. Или, если вы такой же, как я, после попытки выше, все равно не повезло - DRF все еще генерирует чертовы ссылки http. Я хочу поделиться некоторыми советами, когда я отлаживал как ад:

Распечатайте следующие значения в Django, либо print(), если у вас есть DEBUG=True и у вас есть доступ к журналу сервера, либо просто передайте их в контекст шаблона и покажите их на html-странице, если это облегчает вам для тестирования в производственной среде, где включен SSL.

  • request.is_secure(): если вы не можете заставить DRF использовать http, скорее всего, вы получаете False.
  • request.META дает вам все заголовки, которые получил Джанго. Заголовок HTTP_X_FORWARDED_PROTO появился? Какое значение это?
    • Хочу поделиться, что для меня это момент а-ха: значение HTTP_X_FORWARDED_PROTO равно http,https, что говорит мне о том, что оно как-то имеет дублированные значения, и я, вероятно, дважды установил proxy_set_header, и Бинго! По умолчанию входной контроллер K8 уже имеет эту строку, и, поскольку я снова добавил ее через location-snippet, теперь у меня всего две строки proxy_set_header X-Forwarded-Proto $scheme;. После удаления моего фрагмента, DRF показывает https, и я был так счастлив сделать это. Это было не так просто, потому что я думал, что proxy_set_header перезапишет и "установит" значение заголовка. Но, похоже, он просто продолжает добавляться.