Django中如何限制未认证用户访问媒体文件的URL?
Django私有媒体文件访问权限控制解决方案
你的问题根源是默认情况下Django的媒体文件是直接由Web服务器静态分发,不会经过Django的权限校验逻辑,所以只要拿到URL就能直接访问,以下是可落地的解决方案:
方案1:Django视图代理返回文件(小文件场景适用)
适合文件体积小、访问量低的场景,所有文件请求都走Django视图做权限校验:
- 不要直接暴露媒体文件的真实存储路径,将
MEDIA_URL设置为需要权限校验的路由前缀,比如/private-media/ - 编写专用的媒体文件服务视图,在视图内完成用户身份、文件所有权校验,校验通过后才返回文件内容,示例代码:
from django.http import HttpResponse, Http404 from django.contrib.auth.decorators import login_required from .models import UserFile import os from django.conf import settings @login_required def serve_private_media(request, file_path): # 校验当前用户是否是文件所有者 user_file = UserFile.objects.filter(file=file_path, owner=request.user).first() if not user_file: raise Http404 # 拼接真实文件存储路径 full_path = os.path.join(settings.MEDIA_ROOT, file_path) # 读取文件返回响应 with open(full_path, 'rb') as f: response = HttpResponse(f.read(), content_type="application/octet-stream") response['Content-Disposition'] = f'inline; filename="{os.path.basename(full_path)}"' return response
- 配置项目路由,将
/private-media/<path:file_path>路径绑定到上述视图即可。
方案2:X-Accel-Redirect/X-Sendfile 代理(生产环境大文件适用)
适合生产环境大文件场景,由Django完成权限校验,Web服务器负责返回文件内容,性能和静态分发一致:
- 首先配置Web服务器禁止外部直接访问真实媒体路径,以Nginx为例,配置示例:
location /real-media/ { internal; # 仅允许内部请求访问,外部直接访问返回404 alias /path/to/your/media/root/; # 真实媒体文件的本地存储路径 }
- 修改Django视图,权限校验通过后返回
X-Accel-Redirect响应头,告知Nginx返回对应文件,示例代码:
from django.http import HttpResponse, Http404 from django.contrib.auth.decorators import login_required from .models import UserFile import os @login_required def serve_private_media(request, file_path): user_file = UserFile.objects.filter(file=file_path, owner=request.user).first() if not user_file: raise Http404 response = HttpResponse() # 告知Nginx返回对应内部路径的文件 response['X-Accel-Redirect'] = f'/real-media/{file_path}' response['Content-Disposition'] = f'inline; filename="{os.path.basename(file_path)}"' del response['Content-Type'] # 让Nginx自动判断文件Content-Type return response
方案3:签名URL+过期时间(临时访问场景适用)
如果需要给用户提供临时访问链接,避免历史URL长期有效,可以用带签名和过期时间的URL:
- 生成URL时,拼接用户ID、文件ID、过期时间戳,用密钥生成签名,最终URL格式类似
/private-media/xxx.pdf?user=123&expire=1717234567&sign=abc123def - 用户访问时先校验签名是否合法、是否已过期,再校验用户权限,就算用户拿到历史URL,过期后也会自动失效,也无法篡改参数伪造合法链接
- 可以直接用Django内置的
django.core.signing模块实现,示例代码:
from django.core.signing import Signer, BadSignature from datetime import datetime # 生成签名URL signer = Signer() file_path = "xxx.pdf" expire_ts = int(datetime.now().timestamp() + 3600) # 1小时后过期 signed_str = signer.sign(f"{file_path}:{request.user.id}:{expire_ts}") access_url = f"/private-media/{file_path}?sign={signed_str}" # 校验签名 def check_access(sign_str, file_path, current_user_id): try: original = signer.unsign(sign_str) path, uid, exp = original.split(":") return path == file_path and int(uid) == current_user_id and int(exp) > datetime.now().timestamp() except BadSignature: return False
注意事项
- 生产环境禁止直接使用Django默认的
django.views.static.serve分发媒体文件,也不要将MEDIA_ROOT下的路径直接暴露给外部访问 - 所有媒体文件的访问请求必须先经过Django的权限校验逻辑,再返回文件内容或者内部重定向指令
内容的提问来源于stack exchange,提问作者Abdul Halim Chowdhury
相关产品推荐
相关产品推荐

