让 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=Trueurl_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-ForX-Forwarded-ProtoX-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的主要内容,如果未能解决你的问题,请参考以下文章

使用 url_for 链接到 Flask 静态文件

flask 关于 url_for的解析方式

flask 链接 url_for()

[flask]jinjia2-模板 url_for的使用

flask第九篇——url_for

flask第九篇——url_for