Сделайте Flask url_for использовать схему "https" в балансировщике нагрузки AWS, не испортив SSLify
Недавно я добавил SSL-сертификат на мой webapp. Он развернут на Amazon Web Services, используя балансировщики нагрузки. Балансиры нагрузки работают как обратные прокси, обрабатывают внешние HTTPS и отправляют внутренний HTTP. Таким образом, весь трафик для моего приложения Flask - это HTTP, а не HTTPS, несмотря на то, что он является безопасным соединением.
Поскольку сайт был уже включен в сеть до миграции HTTPS, я использовал SSLify для отправки 301 PERMANENT REDIRECTS
в HTTP-соединения. Он работает, несмотря на то, что все соединения являются HTTP, потому что обратный прокси устанавливает заголовок запроса X-Forwarded-Proto
с исходным протоколом.
Проблема
url_for
не интересует X-Forwarded-Proto
. Он будет использовать my_flask_app.config['PREFERRED_URL_SCHEME']
когда схема недоступна, но во время запроса доступна схема. Схема HTTP соединения с обратным прокси.
Поэтому, когда кто-то подключается к https://example.com
, он подключается к балансировщику нагрузки, который затем подключается к Flask с помощью http://example.com
. Flask видит http
и предполагает, что схема - это HTTP, а не HTTPS, поскольку она изначально была.
Это не проблема в большинстве url_for
используемых в шаблонах, но любой url_for
с _external=True
будет использовать http вместо https. Лично я использую _external=True
для rel=canonical
так как я слышал, что это рекомендуется. Кроме того, использование Flask.redirect
приведет к добавлению внешних ссылок с помощью http://example.com
, поскольку заголовок перенаправления должен быть полным URL-адресом.
Если вы перенаправите, например, сообщение о форме, это произойдет.
- Сообщения клиентов
https://example.com/form
- Сервер выдает
303 SEE OTHER
на http://example.com/form-posted
- Затем SSLify выдает
301 PERMANENT REDIRECT
на https://example.com/form-posted
Каждое перенаправление становится 2 перенаправлением из-за SSLify.
Попытки решения
Добавление конфигурации PREFERRED_URL_SCHEME
qaru.site/info/290165/...
my_flask_app.config['PREFERRED_URL_SCHEME'] = 'https'
Не работает, потому что во время запроса есть схема, и вместо этого используется. См. Https://github.com/mitsuhiko/flask/issues/1129#issuecomment-51759359
Обшивка промежуточного программного обеспечения для издевательства HTTPS
qaru.site/info/290165/...
def _force_https(app):
def wrapper(environ, start_response):
environ['wsgi.url_scheme'] = 'https'
return app(environ, start_response)
return wrapper
app = Flask(...)
app = _force_https(app)
Как это было, это не сработало, потому что мне нужно было это приложение позже. Поэтому вместо этого я использовал wsgi_app.
def _force_https(wsgi_app):
def wrapper(environ, start_response):
environ['wsgi.url_scheme'] = 'https'
return wsgi_app(environ, start_response)
return wrapper
app = Flask(...)
app.wsgi_app = _force_https(app.wsgi_app)
Поскольку wsgi_app
вызывается перед любыми обработчиками app.before_request
, это делает SSLify причиной того, что приложение уже находится за защищенным запросом, а затем он не будет перенаправлять HTTP-HTTPS.
Исправление url_for
(Я даже не могу найти, откуда у меня это)
from functools import partial
import Flask
Flask.url_for = partial(Flask.url_for, _scheme='https')
Это может сработать, но Flask выдаст ошибку, если вы установите _scheme
но не _external
. Поскольку большинство моих приложений url_for
являются внутренними, оно вообще не работает.
Ответы
Ответ 1
У меня были такие же проблемы с "redirect" (url_for ('URL')) за базовым балансиром нагрузки AWS, и я решил это с помощью вызова werkzeug.contrib.fixers.ProxyFix в моем коде. пример:
from werkzeug.contrib.fixers import ProxyFix
app = Flask(__name__)
app.wsgi_app = ProxyFix(app.wsgi_app)
ProxyFix(app.wsgi_app)
добавляет поддержку HTTP-прокси к приложению, которое не было разработано с использованием HTTP-прокси. Он устанавливает REMOTE_ADDR, HTTP_HOST из заголовков X-Forwarded.
Ответ 2
Выкапывая исходный код Flask, я узнал, что url_for
использует Flask._request_ctx_stack.top.url_adapter
когда есть контекст запроса.
url_adapter.scheme
определяет используемую схему. Чтобы заставить параметр _scheme
работать, url_for
временно url_adapter.scheme
и затем вернет его обратно, прежде чем функция вернется.
(это поведение обсуждалось в github относительно того, должно ли оно быть предыдущим значением или PREFERRED_URL_SCHEME)
В основном, что я сделал, был установлен url_adapter.scheme
на https с обработчиком before_request. Этот способ не возится с самим запросом, только с вещью, генерирующей URL-адреса.
def _force_https():
# my local dev is set on debug, but on AWS it not (obviously)
# I don't need HTTPS on local, change this to whatever condition you want.
if not app.debug:
from flask import _request_ctx_stack
if _request_ctx_stack is not None:
reqctx = _request_ctx_stack.top
reqctx.url_adapter.url_scheme = 'https'
app.before_request(_force_https)