Django REST框架分页链接不使用HTTPS

svmlkihl  于 2022-11-18  发布在  Go
关注(0)|答案(3)|浏览(159)

我正在为某个DRF端点设置分页,该端点运行良好-然而,当部署在使用HTTPS的服务器上时,指向下一页和上一页的链接是用http://而不是https://形成的。这导致浏览器阻止对下一页/上一页的请求。
我已经仔细检查了初始请求是使用HTTPS发出的,并且this question的第二个答案表明,由于请求是通过HTTPS发出的,因此它应该在形成的URL中使用HTTPS。
同样问题的第一个答案也没有帮助--我把X-Forwarded-Proto行添加到我的nginx配置中,然后重新加载,没有用。
The DRF docs提到reverse()应该像基本Django reverse一样运行,但是很明显,初始请求是HTTPS,而返回的URL是HTTP。
以下是显示初始请求(https://<domain>.com/api/leaderboard/)的几个屏幕截图:

带有包含next: http://<domain>.com/api/leaderboard/?page=2的响应):

我想这将是一个简单的设置,但还没有能够找到任何东西后,搜索这个网站和DRF网站。
这是我的nginx配置:

location / {
    # proxy_pass http://127.0.0.1:9900;
    proxy_set_header X-Forwarded-Host $server_name;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-Proto $scheme;
    add_header P3P 'CP="ALL DSP COR PSAa PSDa OUR NOR ONL UNI COM NAV"';

    root /opt/app/client/dist;
    index index.html index.htm;

}

This question包含了一个非常详细的答案,但最终说url是用与请求相同的协议形成的,但这里似乎不是这样。我需要设置Django SECURE_PROXY_SSL_HEADER吗?我不确定,因为警告说它可能不安全。

gpfsuwkq

gpfsuwkq1#

由于这是第一个在谷歌搜索结果中出现的SO帖子,我想分享我是如何解决这个问题的会很有用。我的帖子有一种Kubernetes的味道,但在引擎盖下的逻辑与其他帖子没有太大的不同。
我在Django的前面使用Kubernetes入口控制器,所以对你来说可能会有所不同,但我会总结一下我的上下文,这样你就可以看看它对你是否有用:
问题
所以我遇到了和OP一样的问题,来自DRF的分页链接总是http,即使我使用https点击了api端点。如果你设置了Django REST框架附带的浏览器API,你可以更清楚地看到这一点(DRF),然后转到api根页面。我看到所有由DRF生成的链接都是http,无论我访问该站点时使用什么协议。

我的上下文

堆栈,从外部世界到Django的顺序:

  1. Kuberenetes Nginx入口控制器(使用letsencrypt设置SSL)
    1.入口规则指向Django服务
  2. Gunicorn启动Django服务器
  3. Django读取settings.py并开始服务请求

如何调试并找出原因

1.转储入口控制器的nginx.conf。如果您不知道如何转储:首先通过kuberctl get pods -n <namespace of your ingress controller>找出pod名称,记下该名称,然后通过kubectl exec -it -n ingress_controller_namespace ingress_controller_pod_name cat /etc/nginx/nginx.conf > nginx.conf转储文件。
1.查看nginx.conf,检查您的网站域的location部分是否有proxy_set_header X-Forwarded-Proto $scheme;,或者类似的设置代理头X-Forwarded-Proto的东西。默认情况下,Kubernetes nginx入口控制器应该已经为您准备了这一行(或等效行)。
1.接下来,请求被定向到Gunicorn。一个问题是,您需要将--forwarded-allow-ips="*"添加到gunicorn命令中,或者如果您知道nginx服务器的IP,您可以将其限制为该IP,这样gunicorn将为您转发头,否则gunicorn将剥离这些头。因此,您将得到类似gunicorn django_server.wsgi:application --forwarded-allow-ips="*" --workers=${PROPER_WORKER_NUM} --log-level info --bind 0.0.0.0:8001的命令。通过指定workers=4,您可以拥有4个worker。--log-level info仅用于调试目的,它是可选的。
1.在Django中,你需要在settings.py中有SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https'),其他的答案已经提到了。你可能会想为什么前缀HTTP_,而在nginx中我们的头文件名称是X-Forwarded-Proto。这是因为WSGI会将前缀添加到它所识别的头文件中。这一行基本上是说,如果头文件HTTP_X_FORWARDED_PROTO等于字符串"https",那么Django会认为这个请求是安全的。这将影响Django的一些行为,例如,您将得到request.is_secure == Truerequest.build_absolute_uri(None) == 'https://...',最重要的是,Django REST框架分页链接现在将使用https!(只要您确实通过https命中api)
好了,现在你可以再次测试了。如果DRF现在给你https-恭喜。或者,如果你和我一样,在尝试了上面的方法后,仍然没有运气- DRF仍然生成该死的http链接。我想分享一些技巧,当我在调试地狱:
在Django中打印出以下值,如果您有DEBUG=True并且可以访问服务器日志,可以通过print()打印出来;如果您可以在启用SSL的生产环境中更轻松地进行测试,则可以将它们传递给模板上下文并在html页面中显示出来。

  • request.is_secure():如果您无法让DRF使用http,则很可能会得到False
  • request.META提供Django收到的所有头文件。头文件HTTP_X_FORWARDED_PROTO显示了吗?它的值是什么?
  • 我想和大家分享的是,这是我的顿悟时刻:HTTP_X_FORWARDED_PROTO的值是https,https,这告诉我它有重复的值,我可能设置了两次proxy_set_header,答对了!默认情况下K8入口控制器已经有了这一行,因为我通过一个location-snippet再次添加了它,现在我总共有两行proxy_set_header X-Forwarded-Proto $scheme;。删除我的代码片段后,DRF显示了https,我很高兴能完成这个任务。它不是那么简单,因为我认为proxy_set_header会覆盖并“设置”头值。但看起来它只是不断地附加。
wf82jlnq

wf82jlnq2#

我需要设置Django SECURE_PROXY_SSL_HEADER吗?我不确定,因为警告说它可能不安全。
是的。但是,你需要注意你在做什么。特别是要确保它从外面掉下X转发原型。

ff29svar

ff29svar3#

这个答案可能不是最好的,但它的工作,没有必要改变nginx和django SECURE_PROXY_SSL_HEADER.
中settings.py:

MY_PROTOCOL = "https"

中views.py:

from django.conf import settings

class MyView(generics.ListAPIView):
    serializer_class = MySerializer
    authentication_classes = [TokenAuthentication]
    permission_classes = [IsAuthenticated]

    def get(self, request, *args, **kwargs):
        response = super().get(self, request, *args, **kwargs)
        if settings.MY_PROTOCOL == "https":
            if response.data["next"]:
                response.data["next"] = response.data["next"].replace(
                "http://", "https://"
            )
            if response.data["previous"]:
                response.data["previous"] = response.data["previous"].replace(
                "http://", "https://"
            )
        return response

相关问题