Flask的url_for生成的是HTTP URL而不是HTTPS

74

我正在使用url_for生成一个重定向URL,当用户注销时使用:

return redirect(url_for('.index', _external=True))
然而,当我将页面更改为https连接时,url_for仍然给我http
我想明确要求url_for在URL开头添加https
你能指导我如何更改吗?我查看了Flask文档,但没有找到。

1
你的 Flask 应用是如何部署的?因为 https 通常由 WSGI 处理程序处理。 - Jakob Bowyer
@JakobBowyer 我正在使用Flask默认的测试部署环境。只需调用 python index.py 即可。这是Flask的wsgi处理程序。还请查看 @leon 的回答。 - Blaise
这些解决方案都无效。因此,不得不将重定向URL作为配置条目添加。 - Martlark
8个回答

77

使用 Flask 0.10,会有比包装 url_for 更好的解决方案。如果您查看 https://github.com/mitsuhiko/flask/commit/b5069d07a24a3c3a54fb056aa6f4076a0e7088c7,已添加了一个 _scheme 参数。这意味着您可以执行以下操作:

url_for('secure_thingy',
        _external=True,
        _scheme='https',
        viewarg1=1, ...)

_scheme设置URL方案,生成类似于https://..而不是http://的URL。但是,默认情况下,Flask仅生成路径(不包括主机或方案),因此您需要包含_external=True才能从/secure_thingy转到https://example.com/secure_thingy


然而,考虑将您的网站仅限于HTTPS。看起来您正在尝试部分强制使用HTTPS,仅适用于少数“安全”路由,但如果链接到安全页面的页面未加密,则无法确保https-URL不会更改。这类似于混合内容

有趣的是他们把_scheme设为私有参数。至少它能工作。 - Maximilian Burszley
3
它带有下划线前缀,以避免与路由参数冲突。我认为私有属性/方法的命名约定在这里不适用。 - Markus Unterwaditzer

59
如果您想影响所有服务器生成的URL方案(url_forredirect),而不是每次调用都设置_scheme,则似乎“正确”的答案是使用WSGI中间件,例如此代码片段:http://flask.pocoo.org/snippets/35/此Flask错误似乎确认这是首选方法。)基本上,如果您的WSGI环境具有environ ['wsgi.url_scheme'] ='https',则url_for将生成https: URL。因为我的服务器部署在Elastic Beanstalk负载均衡器后面,该负载均衡器使用常规HTTP与服务器通信,所以我从url_for获取了http: URL。我的解决方案(特定于Elastic Beanstalk)如下(简化自上面链接的代码片段):
class ReverseProxied(object):
    def __init__(self, app):
        self.app = app

    def __call__(self, environ, start_response):
        scheme = environ.get('HTTP_X_FORWARDED_PROTO')
        if scheme:
            environ['wsgi.url_scheme'] = scheme
        return self.app(environ, start_response)

app = Flask(__name__)
app.wsgi_app = ReverseProxied(app.wsgi_app)

其中与 Elastic Beanstalk 相关的部分是 HTTP_X_FORWARDED_PROTO。其他环境可能有其他方法来确定外部 URL 是否包含 https。如果您只想始终使用 HTTPS,则可以无条件地设置 environ['wsgi.url_scheme'] = 'https'

PREFERRED_URL_SCHEME 不是实现此目的的方法。它在请求正在进行时被忽略


这个解决方案完美地解决了问题!显然,这个问题特别出现在 AWS Elastic Beanstalk 上。当没有使用 Elastic Beanstalk 进行托管时,相同的代码库没有重定向问题。 - Jinesh
1
谢谢这个解决方案!我想补充一下,对于像我这样的其他Flask新手,您可以“堆叠”或添加多个中间件。因此,如果您的代码已经有了类似 app.wsgi_app = ProxyFix(app.wsgi_app) 的行,您仍然应该能够添加aldel的解决方案而不会发生冲突。至少对我来说是这样的。 - N. Quest
针对通过VPC端点访问的私有AWS API网关实现了类似的功能。由于某种原因,头部HTTP_X_FORWARDED_PROTO没有发送到服务器,因此我不得不设置environ['wsgi.url_scheme'] = 'https',无论服务器发送了什么(我不想在任何情况下使用http)。 - Tim Ludwinski
我正在GCP计算引擎上运行一个Flask应用程序,并将我的应用程序加载在某个CRM内部的iframe中。这种解决方案对我的情况有效。 - Gray_Rhino
1
Flask的ProxyFix应该已经处理了这个问题,无需定义自己的中间件。https://werkzeug.palletsprojects.com/en/2.2.x/middleware/proxy_fix/ - undefined
显示剩余4条评论

34

如果您通过类似 Nginx 的反向代理访问您的网站,则 Flask 会正确地检测到方案为 HTTP

Browser -----HTTPS----> Reverse proxy -----HTTP----> Flask

最简单的解决方案是配置反向代理以设置X-Forwarded-Proto头。 Flask将自动检测此标头并相应地管理协议。在Flask文档的代理设置部分中,有一份更详细的说明。例如,如果您使用Nginx,则需要在location块中添加以下行。

proxy_set_header   X-Forwarded-Proto    $scheme;

正如其他人提到的,如果您无法更改代理的配置,您可以使用werkzeug ProxyFix或根据文档中描述的方式构建自己的修复程序: http://flask.pocoo.org/docs/0.12/deploying/wsgi-standalone/#proxy-setups


这是对我有效的正确答案。nginx配置的当前/正确文档链接在这里:https://flask.palletsprojects.com/en/2.3.x/deploying/nginx/ - undefined

34
我尝试了接受的答案,使用了url_for参数,但我发现使用PREFERRED_URL_SCHEME配置变量并将其设置为https更容易:
app.config.update(dict(
  PREFERRED_URL_SCHEME = 'https'
))

由于您不需要将其添加到每个url_for调用中。


1
对我来说最简单、最简洁的答案。 - user2682863

9

在每次调用 url_for() 时设置 _scheme非常繁琐,而且PREFERRED_URL_SCHEME似乎不起作用。但是,在WSGI级别上修改请求的预期方案似乎成功地说服了Flask始终构建HTTPS URL:

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)

9

最近来到这里的任何人,现在有一个官方的uwsgi修复程序: https://dev59.com/N2Ag5IYBdhLWcg3wpMWz#23504684

顺便说一下,对于我来说这仍然不起作用,因为标头没有被正确设置,所以我增加了ReversedProxied中间件,如果找到则优先使用https:

class ReverseProxied(object):
"""
Because we are reverse proxied from an aws load balancer
use environ/config to signal https
since flask ignores preferred_url_scheme in url_for calls
"""

    def __init__(self, app):
        self.app = app

    def __call__(self, environ, start_response):
        # if one of x_forwarded or preferred_url is https, prefer it.
        forwarded_scheme = environ.get("HTTP_X_FORWARDED_PROTO", None)
        preferred_scheme = app.config.get("PREFERRED_URL_SCHEME", None)
        if "https" in [forwarded_scheme, preferred_scheme]:
            environ["wsgi.url_scheme"] = "https"
        return self.app(environ, start_response)

被称为:

app = flask.Flask(__name__)
app.wsgi_app = ReverseProxied(app.wsgi_app)

如果你已经明确设置了环境变量 "PREFERRED_URL_SCHEME",或者nginx等代理服务器设置了X_FORWARDED_PROTO,那么它就会做正确的事情。


1
这个 shim 是必要的,以便在运行 gunicorn 的 Google Cloud Run 容器中正确地使用 Flask + OAuth2。 - postal
需要注意的是,preferred_scheme = app... 中的 app 不是 self.app(WSGI 应用程序),而是 Flask 应用程序。 - moi

0
我个人无法通过这里的任何答案解决这个问题,但是发现只需在flask运行命令的末尾添加--cert=adhoc即可使flask应用程序以https方式运行,从而解决了这个问题。

flask run --host=0.0.0.0 --cert=adhoc


-1
ingress:
  web:
    enabled: true
    annotations:
      kubernetes.io/ingress.class: "nginx"
      nginx.ingress.kubernetes.io/rewrite-target: /
      nginx.ingress.kubernetes.io/use-regex: "true"
      nginx.ingress.kubernetes.io/configuration-snippet: |
        proxy_set_header X-Forwarded-Proto https;
        proxy_set_header X-Forwarded-Port 443;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";

https://github.com/apache/airflow/discussions/31805


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接