在 Django 中限制对私有文件下载的访问

Posted

技术标签:

【中文标题】在 Django 中限制对私有文件下载的访问【英文标题】:Restricting access to private file downloads in Django 【发布时间】:2015-03-25 20:23:26 【问题描述】:

我的 django 应用中有多个 FileField,它们可以属于不同的用户。 我正在寻找一种限制非文件所有者用户访问文件的好方法。

实现这一目标的最佳方法是什么?有什么想法吗?

【问题讨论】:

我建议你关注How to to make a file private by securing the url that only authenticated users can see,但你必须实现自己的方法来限制其他用户不能访问...! 【参考方案1】:

不幸的是,@Mikko 的解决方案实际上无法在生产环境中运行,因为 django 不是为提供文件而设计的。在生产环境中,文件需要由您的 HTTP 服务器(例如 apache、nginx 等)提供,而由您的应用程序/django 服务器(例如 uwsgi、gunicorn、mod_wsgi 等)提供。

这就是限制文件访问的原因不是很容易:您需要一种方法让您的 HTTP 服务器询问应用程序服务器是否可以将文件提供给请求它的特定用户。正如您所理解的,这需要修改您的应用程序您的 http 服务器。

上述问题的最佳解决方案是 django-sendfile (https://github.com/johnsensible/django-sendfile),它使用 X-SendFile 机制来实现上述问题。我从项目的描述中复制:

这是一个用于将文件发送到 Web 客户端的 Web 服务器特定方法的包装器。当 Django 需要检查权限相关文件,但不想提供文件本身的实际字节时,这很有用。也就是说,为大文件提供服务并不是 Django 的用途。

要了解更多关于 senfile 机制,请阅读这个答案:Django - Understanding X-Sendfile

2018 年更新:请注意 django-sendfile 似乎不再维护;可能它仍然可以工作,但是如果您想要一个具有类似功能的更现代的包,请查看https://github.com/edoburu/django-private-storage,正如评论者@surfer190 所建议的那样。尤其要确保你实现了“优化大文件传输”部分;实际上,所有传输都需要这个,而不仅仅是大文件。

2021 年更新:我回到这个答案指出,虽然它已经有 4 年没有更新了,但 django-sendfile 项目仍然适用于当前的 Django 版本( 3.2)我实际上将它用于我所有需要该特定功能的项目!现在还有一个积极维护的 fork,django-sendfile2,它改进了 Python 3 支持和更广泛的文档。

【讨论】:

虽然,django-sendfile 是一个不错的包。如果您使用github.com/edoburu/django-private-storage,则自定义代码较少 @surfer190 你提到的包现在似乎是一个很好的解决方案,因为 django-sendfile 似乎不再维护了:( 我将更新答案以包括这个包。但是请注意,当我首先回答了 django-private-storage 实际上并不存在的问题!【参考方案2】:

如果您只需要适度的安全性,我的方法如下:

1) 当用户上传文件时,为其生成一个难以猜测的路径。例如,您可以为 /static 文件夹中的每个上传文件创建一个具有随机生成名称的文件夹。您可以使用以下示例代码非常简单地做到这一点:

file_path = "/static/" + os.urandom(32).encode('hex') + "/" + file_name

这样就很难猜测其他用户的文件存储在哪里了。

2) 在数据库中将所有者链接到文件。示例架构可以是:

uploads(id, user_id, file_path)

3) 在模型中使用 FileFields 的属性来限制对文件的访问,方式如下:

class YourModel(models.Model)
    _secret_file = models.FileField()

    def get_secret_file(self):
        # check in db if the user owns the file
        if True:
            return self._secret_file
        elif:
            return None # or something meaningful depanding on your app

    secret_file = property(get_secret_file)

【讨论】:

【参考方案3】:

这最好由服务器处理,例如nginxsecure link module(nginx必须用--with-http_secure_link_module编译)

文档中的示例:

location /some-url/ 
    secure_link $arg_md5,$arg_expires;
    secure_link_md5 "$secure_link_expires$uri$remote_addr some-secret";

    if ($secure_link = "") 
        return 403;
    

    if ($secure_link = "0") 
        return 410;
    

    if ($secure_link = "1") 
        // authorised...
    

文件的访问方式如下:

/some-url/some-file?md5=_e4Nc3iduzkWRm01TBBNYw&expires=2147483647

(这将是有时间限制的,并且绑定到该 IP 地址的用户)。

生成要传递给用户的令牌将使用类似:

echo -n 'timestamp/some-url/some-file127.0.0.1 some-secret' | \
openssl md5 -binary | openssl base64 | tr +/ -_ | tr -d =

【讨论】:

【参考方案4】:

通常,您不会通过直接通过 Apache、Nginx 或您使用的任何 Web 服务器提供的普通静态文件来路由私有文件。而是编写一个自定义的 Django 视图来处理权限检查,然后将文件作为流式下载返回。

确保文件位于特殊的私有文件夹文件夹中,并且不会通过 Django 的 MEDIA_URLSTATIC_URL 公开

写一个视图

检查用户是否有权访问您的视图逻辑中的文件

用Python的open()打开文件

返回HTTP响应,获取文件句柄作为参数http.HttpResponse(_file, content_type="text/plain")

例如查看download() here。

【讨论】:

【参考方案5】:
对于那些使用 Nginx 作为网络服务器来提供文件的用户,“X-Accel-Redirect”是一个不错的选择。

首先,对文件的访问请求来自 Django,经过身份验证和授权后,它在内部使用“X-Accel-Redirect”重定向到 Nginx。有关此标头的更多信息:X-Accel-Redirect

请求到达 Django 并将被检查如下:

if user_has_right_permission
    response = HttpResponse()
    response['X-Accel-Redirect'] = path_to_file
    return response
else:
    raise PermissionDenied()

如果用户有正确的权限,它会重定向到 Nginx 来提供文件。

Nginx 的配置是这样的:


server 
    listen 81;
    listen [::]:81;
    
    ...
    location /media/ 
        internal; can be accessed only internally
        alias /app/media/;
    
    ...

注意:关于 path_to_file 的事情是它应该以“/media/”开头以供 Nginx 服务(虽然很清楚)

【讨论】:

以上是关于在 Django 中限制对私有文件下载的访问的主要内容,如果未能解决你的问题,请参考以下文章

从令牌创建 Django 会话

修饰符

Django Admin Cookbook-18如何限制对Django Admin管理部分功能的使用

我们使用访问修饰符来限制用户对受保护/私有变量的访问,但用户怎么可能尝试访问它们呢?

如何将 django 端点限制为本地网络?

AWS S3存储桶Django 3.0用户配置文件图像上传访问错误