You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

无专用服务器时,如何通过Nginx限制Django项目媒体文件访问

问题描述

我在Docker容器中运行Django项目,采用uWSGI作为协议、Nginx充当反向代理。通过user.is_authenticated()可以限制用户访问Django站点,但无法限制未认证用户访问媒体文件和静态文件——因为这些文件直接从文件系统提供。我没有专门的认证服务器,希望利用Django已有的认证功能实现访问限制。

当前配置

Nginx配置

events{}

daemon off;


http {
    access_log /dev/stdout;
    error_log /var/log/nginx/error.log;

    upstream django {
        server unix:///tmp/nginx/diplab.sock;
    }

    server {
    listen 8080;

    location = /accounts/check-authenticated {
        internal;
        uwsgi_pass              django;
        proxy_pass_request_body off;
        proxy_set_header        Content-Length "";
        }

    location /static {
        alias /vol/web/static;
        include /etc/nginx/mime.types;
        }

    location /media {
        alias /vol/web/media;
        include /etc/nginx/mime.types;
        auth_request /accounts/check-authenticated;
        auth_request_set $auth_status $upstream_status;
        }

    location / {
        uwsgi_pass django;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        include /etc/nginx/uwsgi_params;
        }
    }
}

Django认证视图配置

urls.py

# urls.py
app_name = 'accounts'
urlpatterns = [
    path('check-authenticated/', views.is_authenticated_view, name="check-authenticated"),
    path('login/', views.UserLoginView.as_view(), name='login'),
    path('logout/', auth_views.LogoutView.as_view(), name='logout'),
]

views.py

# views.py
def is_authenticated_view(request):
    if request.user.is_authenticated:
        return HttpResponse(status=200)
    return HttpResponse(status=401)

出现的错误

Django核心报错

Traceback (most recent call last):
  File "/root/miniconda3/envs/myproject/lib/python3.10/site-packages/django/core/handlers/wsgi.py", line 130, in __call__
    request = self.request_class(environ)
  File "/root/miniconda3/envs/myproject/lib/python3.10/site-packages/django/core/handlers/wsgi.py", line 78, in __init__
    self.method = environ["REQUEST_METHOD"].upper()
KeyError: 'REQUEST_METHOD'

Nginx error.log

2022/12/06 17:42:27 [error] 194#194: *1 upstream prematurely closed connection while reading response header from upstream, client: 172.19.0.1, server: , request: "GET /media/myname/compound_structures/1cf10238af0.png HTTP/1.1", subrequest: "/accounts/check-authenticated", upstream: "uwsgi://unix:///tmp/nginx/diplab.sock:", host: "127.0.0.1:8080", referrer: "http://127.0.0.1:8080/toolbox/"
2022/12/06 17:42:27 [error] 194#194: *1 auth request unexpected status: 502 while sending to client, client: 172.19.0.1, server: , request: "GET /media/myname/compound_structures/1cf10238af0.png HTTP/1.1", host: "127.0.0.1:8080", referrer: "http://127.0.0.1:8080/toolbox/"

我尝试添加REQUEST_METHOD及其他变量,但仍出现相同错误:

location = /accounts/check-authenticated {
    internal;
    uwsgi_pass              django;
    proxy_pass_request_body off;
    proxy_set_header        Content-Length "";
    proxy_set_header        REQUEST_METHOD GET;
    proxy_set_header X-Original-URI $request_uri;
    proxy_set_header X-Original-Remote-Addr $remote_addr;
    proxy_set_header X-Original-Host $host;
    }

编辑1

按照建议在location块中加入了uwsgi_params:

location = /accounts/check-authenticated {
    internal;
    uwsgi_pass              django;
    proxy_pass_request_body off;
    proxy_set_header        Content-Length "";
    proxy_set_header        X-Original-URI $request_uri;
    include /etc/nginx/uwsgi_params;
    }

这解决了部分问题,但图片仍无法显示:

uwsgi日志

172.19.0.1 - - [07/Dec/2022:08:33:33 +0000] "GET /media/myname/compound_structures/1cf10238af0.png HTTP/1.1" 500 186 "-" "Mozilla/5.0 (X11; Linux x86_64; rv:91.0) Gecko/20100101 Firefox/91.0"
[pid: 219|app: 0|req: 19/19] 172.19.0.1 () {52 vars in 936 bytes} [Wed Dec  7 08:33:33 2022] GET /media/myname/compound_structures/1cf10238af0.png => generated 0 bytes in 1 msecs (HTTP/1.1 301) 6 headers in 239 bytes (1 switches on core 0)

Nginx error.log

2022/12/07 08:33:33 [error] 195#195: *30 auth request unexpected status: 301 while sending to client, client: 172.19.0.1, server: , request: "GET /media/myname/compound_structures/1cf10238af0.png HTTP/1.1", host: "127.0.0.1:8080"

解决方案

问题根源分析

  1. 初始KeyError: 'REQUEST_METHOD':Nginx的auth_request子请求默认用GET方法,但你在/accounts/check-authenticated的location块中未包含uwsgi_params,导致uWSGI未向Django传递必要的CGI环境变量(比如REQUEST_METHOD)。
  2. 后续301错误:认证视图返回了重定向而非预期的200/401,原因是Django默认会对末尾无斜杠的URL返回301重定向到带斜杠的版本,而Nginx的子请求路径与Django的URL匹配不一致。

修复步骤

步骤1:修正Nginx认证请求location配置

用uwsgi_param替代proxy_set_header(因为uwsgi_pass对应uWSGI协议,proxy_set_header仅对HTTP代理有效),并确保包含uwsgi_params:

location = /accounts/check-authenticated {
    internal;
    uwsgi_pass django;
    uwsgi_param REQUEST_METHOD GET;
    uwsgi_param CONTENT_LENGTH "";
    include /etc/nginx/uwsgi_params;
}

步骤2:修正Django URL与视图匹配

两种方案二选一:

  • 方案A:统一URL斜杠规则
    确保Nginx的auth_request路径与Django的URL完全匹配。如果Django的URL是check-authenticated/(带斜杠),则Nginx的配置改为:

    auth_request /accounts/check-authenticated/;
    

    或者在Django的settings.py中设置APPEND_SLASH = False(不推荐,除非你明确不需要自动补斜杠)。

  • 方案B:视图中避免重定向
    直接在视图中返回状态码,不让Django触发自动重定向:

    def is_authenticated_view(request):
        return HttpResponse(status=200 if request.user.is_authenticated else 401)
    

步骤3:确保会话Cookie传递

uwsgi_params默认包含HTTP_COOKIE参数,只要正确包含该文件,Nginx就会把原始请求的Cookie传递给Django,保证认证状态能被正确识别。

步骤4:静态文件访问限制(可选)

如果需要限制静态文件访问,给/static的location块添加相同的认证配置:

location /static {
    alias /vol/web/static;
    include /etc/nginx/mime.types;
    auth_request /accounts/check-authenticated;
    auth_request_set $auth_status $upstream_status;
}

验证修复

  1. 重启服务:
    # 重启Nginx
    nginx -s reload
    # 重启uWSGI(根据你的启动方式调整)
    uwsgi --reload /path/to/uwsgi.pid
    
  2. 测试未认证用户访问媒体文件:应返回401状态码。
  3. 测试已认证用户访问:应正常加载媒体文件。

内容的提问来源于stack exchange,提问作者Tarquinius

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.09 02:41:27