内容简介:作者: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 复制代码
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) 复制代码
ImageField
是 FileField
的一个特殊字段,用 Pillow
来确认一个文件是否是图片。 ImageField
和 FileField
使用Django的 文件存储API
来工作(提供了一种读取文件的方式),同时可以进行文件的读写。 Django自带了 FileSystemStorage
,实现了存储API将文件数据存储到本地文件系统上。这对开发来说足够了,但后续我们会考虑替代方案。
我们用 ImageField
的 upload_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 复制代码
视图再一次做了验证和保存模型的所有工作。我们从请求的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.DEBUG
是 False
, static()
函数将返回一个空列表。
天星技术团QQ: 557247785
。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- struts实战--添加功能(重点文件上传)
- 实战maven私有仓库三部曲之二:上传到私有仓库
- 「小程序JAVA实战」小程序视频上传方法的抽象复用(56)
- Flutter 混合开发实战问题记录(三)打包并上传flutter aar 到maven
- 图文:Apache实战 搭建Web站点(Windows本地上传Web程序至Linux服务器)
- axios上传图片,koa2接收保存上传的图片,lrz在上传前压缩图片
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。