Django2 Web 实战03-文件上传

栏目: Python · 发布时间: 6年前

内容简介:作者:Hubery 时间:2018.10.31接上文:接上文:视频是一种可视化媒介,因此视频数据库至少应该存储图像。让用户上传文件是个很大的隐患,因此接下来会讨论这俩话题:文件上传,安全隐患。

作者:Hubery 时间:2018.10.31

接上文:接上文: Django2 Web 实战02-用户注册登录退出

视频是一种可视化媒介,因此视频数据库至少应该存储图像。让用户上传文件是个很大的隐患,因此接下来会讨论这俩话题:文件上传,安全隐患。

  • 新增一个文件上传函数,让用户给movie上传图片
  • 检查OWASP列举的前10项安全隐患

我们会检查文件上传的安全隐患。可以看下Django帮我们做了什么,以及什么地方我们应该做出谨慎的决策。

1. 文件上传

这里,我们会创建一个model,展示和管理要上传到网站上的文件;然后,创建一个form和视图来验证和处理上传过程。

1.1 准备文件上传配置项

开始着手文件上传之前,我们需要知道,文件上传取决于一系列的设置,且这些设置在开发环境和生产环境上是不同的。这些设置会影响文件的存储方式和访问方式。 Django有两套文件配置:STATIC_* 和MEDIA_*。 Static 文件是我们项目的一部分,比如(CSS,JS)。 Media 文件是用户上传到我们系统中的文件。Media文件不应被信任,切不能执行。 我们将会在 settings.py 文件中设置这两个地方:

MEDIA_URL = '/uploaded'
MEDIA_ROOT = os.path.join(BASE_DIR, '../media_root')
复制代码

MEDIA_URL , 是用来给上传的文件服务的URL。 开发环境 中,这个值无关紧要,同样不会与我们视图中的URL冲突。 生产环境 中,上传的文件应该给一个与我们工程中任何app不同的域URL,同时还不能是子域。 用户的浏览器被欺骗执行它从同一域(或子域)中请求来的文件,因为我们的app将信任该与我们用户cookie(包括session ID)相同的文件。 所有浏览器的默认策略是:同源策略(Same Origin Policy)。 MEDIA_ROOT 是Django保存代码目录的路径。 我们应该确保该目录不在我们的工程代码目录下,这样就不会意外的将该目录加入版本控制范围,或者意外的授予该目录文件一些特定的权限,如执行。 在生产环境中,还有其他的配置项需要配置,如限制请求body等,这些会在后续的部分讨论。

接下来,创建media_root目录: 命令行至: 与我们的项目最外层目录平级

mkdir media_root
ls 
复制代码
Django2 Web 实战03-文件上传

1.2 创建MovieImage模型

MovieImage模型用一个新的字段ImageField来存储文件,同时也会验证该文件是否是图片。尽管ImageField会验证该字段,但仅仅靠阻止那些制造恶意文件的用户是不够的(但会帮助意外点击.zip文件的用户,而不是.png的用户)。 Django用 Pillow 库来做验证,所以先添加Pillow库到环境中:

pip install Pillow
复制代码

默认在命令行中直接pip install Pillow,安装的是最新版本; 另外提供一种更优雅的命令行安装方式:

touch requirements.dev.txt //创建文件
vi requirements.dev.txt // 编辑文件
// 输入版本号 Pillow<4.4.0 然后保存
pip install -r requirements.dev.txt // 执行py库安装
复制代码

接下来开始创建model: core/models.py

def movie_directory_path_with_uuid(instance, filename):
    return '{}/{}'.format(instance.movie_id, uuid4())

class MovieImage(models.Model):
    image = models.ImageField(
        upload_to=movie_directory_path_with_uuid)
    uploaded = models.DateTimeField(
        auto_now_add=True)
    movie = models.ForeignKey(
        'Movie', on_delete=models.CASCADE)
    user = models.ForeignKey(
        settings.AUTH_PASSWORD_VALIDATORS,
        on_delete=models.CASCADE)
复制代码

ImageFieldFileField 的一个特殊字段,用 Pillow 来确认一个文件是否是图片。 ImageFieldFileField 使用Django的 文件存储API 来工作(提供了一种读取文件的方式),同时可以进行文件的读写。 Django自带了 FileSystemStorage ,实现了存储API将文件数据存储到本地文件系统上。这对开发来说足够了,但后续我们会考虑替代方案。

我们用 ImageFieldupload_to 参数来指定一个方法,用来生成上传文件的名字。我们不希望用户可以在我们的系统中指定文件的名字,因为他们可能会滥用一些用户信任的名字,从而使我们难堪。鉴于此,我们使用一个函数将指定的movie的所有图片存储在同一目录中,同时用 uuid4 为每个文件生成一个 通用 的名字(这也避免了 名字冲突 和处理 文件之间的相互覆盖 问题)。

我们同时会记录是谁上传的文件,这样如果我们发现一个坏的文件,相当于提供了一种如何找到其他坏文件的线索。

模型创建完,更新数据库:

python manage.py makemigrations core
复制代码

有了模型,就可以创建其他部分,如表单和视图。

1.3 创建和使用MovieImageForm

MovieImageForm和之前的VoteForm相似,它会隐藏和禁用模型所需的movie和user字段,这很难取得客户的信任。

编辑core/forms.py

# 添加文件上传form
class MovieImageForm(forms.ModelForm):
    movie = forms.ModelChoiceField(
        widget=forms.HiddenInput,
        queryset=Movie.objects.all(),
        disabled=True,
    )

    user = forms.ModelChoiceField(
        widget=forms.HiddenInput,
        queryset=get_user_model().objects.all(),
        disabled=True,
    )

    class Meta:
        model = MovieImage
        fields = ('image', 'user', 'movie')
复制代码

表单ModelForm中,我们没有重写MovieImage的image字段,因为ModelForm会自动提供一正确的文件选择框:<input type="file">。

现在我们在视图MovieDetail中使用这个表单, core/views.py:

# movie详情 视图
class MovieDetail(DetailView):
 	 queryset = Movie.objects.all_with_related_persons_and_score()

    def get_context_data(self, **kwargs):
    ctx = super().get_context_data(**kwargs)
		# 配置图片上传表单
	  ctx['image_form'] = self.movie_image_form()
       # 其他 略

    # 添加图片上传表单
    def movie_image_form(self):
        if self.request.user.is_authenticated:
            return MovieImageForm()
        return None
复制代码

这里的上传代码比较简单,只能上传新图片,没有其他操作,一只提供一个空表单。然而通过这种方式我们不能显示错误信息。实践中,丢失error信息不是很好的做法。

1.4 更新模版movie_detail.html显示和上传图片

我们需要对movie_detail.html模版进行两次更新。

  • 需要更新main模版的block新增一个图片列表。
  • 需要更新sidebar模版的block包含我们新建的上传表单。

编辑core/templates/core/movie_detail.html

{% extends 'base.html' %}

{% block title %}
    {{ object.title }} - {{ block.super }}
{% endblock %}

{% block main %}
    <h1>{{ object }}</h1>
    <p class="lead">
        {{ object.plot }}
    </p>

    {# 展示电影图片列表 #}
    <div class="col">
        <h1>{{ object }}</h1>
        <p class="lead"> {{ object.plot }}</p>
    </div>

    <ul>
        {% for i in object.movieimage_set.all %}
            <li class="list-inline-item">
                <img src="{{ i.image.url }}">
            </li>
        {% endfor %}
    </ul>
    <p>由 {{ object.director }} 执导。</p>
{% endblock %}

{% block sidebar %}
    {# 电影排名部分 #}
    <div>
        这个电影排名:
        <span class="badge badge-primary">
        {{ object.get_rating_display }}
    </span>
    </div>

    <div>
        <h2>
            该片得分:{{ object.score|default_if_none:"TBD-暂无得分" }}
        </h2>
    </div>

    {# 文件上传部分 #}
    {% if image_form %}

        <div>
            <h2>上传新图片</h2>
            <form method="post"
            enctype="multipart/form-data"
            action="{% url 'core:MovieImageUpload' movie_id=object.id %}">

                {% csrf_token %}
                {{ image_form.as_p }}
                <p>
                    <button class="but btn-primary">上传</button>
                </p>
            </form>
        </div>
    {% endif %}

    {# 投票部分 #}
    {% if vote_form %}
        <form method="post" action="{{ vote_form_url }}">
            {% csrf_token %}
            {{ vote_form.as_p }}
            <button class="btn btn-primary">投票</button>
        </form>
    {% else %}
        <p> 先登录,再给此电影投票</p>
    {% endif %}
{% endblock %}
复制代码

更新movie_detail.html的main和sidebar部分。 main block 中,用 image 字段的 url 属性,返回 MEDIA_URL 中设置的URL,再与计算的名字相拼接,然后我们可以通过tag找到正确的图片。 sidebar block 中,form tag中一定要引入enctype属性,以便可以让上传的文件与请求的属性相关联。

模版升级完成,可以开始创建保存上传文件的视图了:MovieImageUpload。

1.5 创建MovieImageUpload视图

编辑core/views.py文件

# 创建图片上传视图
class MovieImageUpload(LoginRequiredMixin, CreateView):
    form_class = MovieImageForm

    def get_initial(self):
        initial = super().get_initial()
        initial['user'] = self.request.user.id
        initial['movie'] = self.kwargs['movie_id']
        return initial

    def render_to_response(self, context, **response_kwargs):
        movie_id = self.kwargs['movie_id']
        movie_detail_url = reverse(
            'core:MovieDetail',
            kwargs={'pk': movie_id})
        return redirect(to=movie_detail_url)

    def get_success_url(self):
        movie_id = self.kwargs['movie_id']
        movie_detail_url = reverse(
            'core:MovieDetail', kwargs={'pk': movie_id})
        return movie_detail_url
复制代码
Django2 Web 实战03-文件上传

视图再一次做了验证和保存模型的所有工作。我们从请求的user属性中获取user.id属性,从URL中获取movie ID,当MovieImageForm的user和movie字段不可用时(忽略请求body体中的参数值),将user和movie ID当作初始参数传给form。 Django的ImageField会对文件改名和存储。

1.6 将请求关联到视图和文件上

将文件上传视图MovieImageUpload关联到URLConf中。 编辑core/urls.py

from django.conf.urls import url
from django.urls import path

from core import views

app_name = 'core'

urlpatterns = [
    # 省略其他路径
    # 配置
    path('movie/<int:movie_id>/image/upload',
         views.MovieImageUpload.as_view(),
         name='MovieImageUpload'),
]
复制代码

像往常一样,我们添加一个path()函数,确保传入一个movie_id参数。 现在Django就知道如何找到我们新增的文件上传视图,只是它还不知道如何对外提供这个上传的文件。 在开发环境中,为了对外提供该上传的文件,更新下urls.py文件: MyMovie/urls.py

from django.conf import settings
from django.conf.urls.static import static

from django.contrib import admin
from django.urls import path, include

import core.urls
import user.urls

MEDIA_FILE_PATHS = static(
    settings.MEDIA_URL,
    document_root=settings.MEDIA_ROOT)

urlpatterns = [
    path('admin/', admin.site.urls),
    path('user/', include(user.urls, namespace='user')),
    path('', include(core.urls, namespace='core')),
] + MEDIA_FILE_PATHS
复制代码

Django提供了 static() 函数,返回一个包含单路径对象的列表,该对象将以字符串 MEDIA_URL 开头的任何请求路由到 document_root 中的文件。 开发环境中,这给我们提供了一种上传图片文件的方式。这种方式不适合生产环境,如果 settings.DEBUGFalsestatic() 函数将返回一个空列表。

天星技术团QQ: 557247785

Django2 Web 实战03-文件上传

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Bulletproof Web Design

Bulletproof Web Design

Dan Cederholm / New Riders Press / 28 July, 2005 / $39.99

No matter how visually appealing or packed with content a Web site is, it isn't succeeding if it's not reaching the widest possible audience. Designers who get this guide can be assured their Web site......一起来看看 《Bulletproof Web Design》 这本书的介绍吧!

CSS 压缩/解压工具
CSS 压缩/解压工具

在线压缩/解压 CSS 代码

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试

RGB HSV 转换
RGB HSV 转换

RGB HSV 互转工具