Недавно я добавил 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
my_flask_app.config['PREFERRED_URL_SCHEME'] = 'https'
Не работает, потому что во время запроса есть схема, и вместо этого используется. См. Https://github.com/mitsuhiko/flask/issues/1129#issuecomment-51759359
Обшивка промежуточного программного обеспечения для издевательства HTTPS
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
являются внутренними, оно вообще не работает.