让 Flask 的 url_for 在 AWS 负载均衡器中使用“https”方案,而不会弄乱 SSLify
Posted
技术标签:
【中文标题】让 Flask 的 url_for 在 AWS 负载均衡器中使用“https”方案,而不会弄乱 SSLify【英文标题】:Make Flask's url_for use the 'https' scheme in an AWS load balancer without messing with SSLify 【发布时间】:2016-04-20 12:43:39 【问题描述】:我最近向我的 web 应用程序添加了 SSL 证书。它部署在 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
时,它会连接到负载均衡器,然后负载均衡器使用http://example.com
连接到 Flask。 Flask 看到 http
并假设该方案是 HTTP,而不是原来的 HTTPS。
在模板中使用的大多数url_for
中这不是问题,但任何带有_external=True
的url_for
都将使用http 而不是https。就个人而言,我使用_external=True
代替rel=canonical
,因为我听说这是推荐的做法。除此之外,使用Flask.redirect
会在非_external url 前面加上http://example.com
,因为重定向标头必须是完全限定的URL。
例如,如果您在表单帖子上重定向,就会发生这种情况。
-
客户发帖
https://example.com/form
服务器向http://example.com/form-posted
发出303 SEE OTHER
SSLify 然后向https://example.com/form-posted
发出301 PERMANENT REDIRECT
因为 SSLify,每个重定向变成 2 个重定向。
尝试的解决方案
添加 PREFERRED_URL_SCHEME 配置
https://***.com/a/26636880/1660459
my_flask_app.config['PREFERRED_URL_SCHEME'] = 'https'
不起作用,因为在请求期间有一个方案,而是使用了那个方案。见https://github.com/mitsuhiko/flask/issues/1129#issuecomment-51759359
封装一个中间件来模拟 HTTPS
https://***.com/a/28247577/1660459
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')
这可行,但如果你设置_scheme
而不是_external
,Flask 会报错。由于我的大部分应用程序url_for
都是内部应用程序,因此它根本不起作用。
【问题讨论】:
【参考方案1】:挖掘 Flask 源代码,我发现url_for
在有请求上下文时使用Flask._request_ctx_stack.top.url_adapter
。
url_adapter.scheme
定义了使用的方案。为了使_scheme
参数起作用,url_for
将临时交换url_adapter.scheme
,然后在函数返回之前将其设置回来。
(这个行为已经在github上讨论过,应该是之前的值还是PREFERRED_URL_SCHEME)
基本上,我所做的是使用 before_request 处理程序将 url_adapter.scheme
设置为 https。这种方式不会弄乱请求本身,只会弄乱生成 url 的东西。
def _force_https():
# my local dev is set on debug, but on AWS it's 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)
【讨论】:
【参考方案2】:我最近在 AWS Elastic Load Balancer 后面遇到了与 `redirect(url_for('URL'))' 相同的问题,我使用 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 代理的应用程序。它从 X-Forwarded 标头中设置 REMOTE_ADDR、HTTP_HOST。
例子:
from werkzeug.middleware.proxy_fix import ProxyFix
# App is behind one proxy that sets the -For and -Host headers.
app = ProxyFix(app, x_for=1, x_host=1)
【讨论】:
这是修复。非常感谢! 这是一个超级英雄。 非常感谢您的修复!将其与 AWS ELB 和 EC2 实例一起用作目标 因为 werkzeug 0.15 (werkzeug.palletsprojects.com/en/0.15.x/middleware/proxy_fix) 它应该是from werkzeug.middleware.proxy_fix import ProxyFix; app = ProxyFix(app, x_for=1, x_host=1, x_proto=1, x_port=1)
- 类被移动到 midleware
包并且有必要设置 x_...
参数以使其实际使用标题 ( x_host 用于 X-Forwarded-Host 等其他预期标头)。
根据 AWS 文档 (docs.aws.amazon.com/elasticloadbalancing/latest/application/…),仅设置了以下标头:X-Forwarded-For
、X-Forwarded-Proto
、X-Forwarded-Port
。不要将 x_host 设置为受信任的。此外,python 路径已更改为from werkzeug.middleware.proxy_fix import ProxyFix
。请更新到 Flask 应用程序:app.wsgi_app = ProxyFix(app.wsgi_app, x_for=1, x_proto=1, x_port=1)
.以上是关于让 Flask 的 url_for 在 AWS 负载均衡器中使用“https”方案,而不会弄乱 SSLify的主要内容,如果未能解决你的问题,请参考以下文章