限制访问 Django 中的私人文件下载

Restricting access to private file downloads in Django

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

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

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

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

  • 公开
  • 写一个视图

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

    • 用Python的open()

    • 打开文件
    • Return 获取文件句柄作为参数的 HTTP 响应 http.HttpResponse(_file, content_type="text/plain")

例如参见 [​​=14=] here

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

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

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

This is a wrapper around web-server specific methods for sending files to web clients. This is useful when Django needs to check permissions associated files, but does not want to serve the actual bytes of the file itself. i.e. as serving large files is not what Django is made for.

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

2018更新:请注意,django-sendfile似乎不再维护了;可能它应该仍然有效,但是如果您想要一个具有类似功能的更现代的软件包,请按照评论者@surfer190 的建议查看 https://github.com/edoburu/django-private-storage。特别要确保您实施了“优化大文件传输”部分;实际上,您不仅需要大文件的所有传输都需要它。

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

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

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

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

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

2) 在数据库中 link 文件的所有者。示例架构可以是:

uploads(id, user_id, file_path)

3) 在模型中为您的文件字段使用 属性 来限制对文件的访问,这样:

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)

这最好由服务器处理,例如nginx secure 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 =
对于那些使用 Nginx 作为网络服务器来提供文件的人来说,'X-Accel-Redirect' 是一个不错的选择。

首先,访问文件的请求到达 Django,经过身份验证和授权后,它在内部重定向到 'X-Accel-Redirect' 的 Nginx。关于这个的更多信息 header:X-Accel-Redirect

请求到达 Django,将像下面这样检查:

if user_has_right_permission
    response = HttpResponse()
    # Let nginx guess to correct file mime-type by setting
    # below header empty. otherwise all the files served as
    # plain text
    response['Content-Type'] = ''
    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 服务(虽然很清楚)