无专用服务器时,如何通过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"
问题根源分析
- 初始
KeyError: 'REQUEST_METHOD':Nginx的auth_request子请求默认用GET方法,但你在/accounts/check-authenticated的location块中未包含uwsgi_params,导致uWSGI未向Django传递必要的CGI环境变量(比如REQUEST_METHOD)。 - 后续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; }
验证修复
- 重启服务:
# 重启Nginx nginx -s reload # 重启uWSGI(根据你的启动方式调整) uwsgi --reload /path/to/uwsgi.pid - 测试未认证用户访问媒体文件:应返回401状态码。
- 测试已认证用户访问:应正常加载媒体文件。
内容的提问来源于stack exchange,提问作者Tarquinius

