本博客包含多个文档和书籍的翻译,但有能力者推荐阅读英文原版

Django 3网页开发指南第4版 第7章 安全和性能

Django Alan 3周前 (06-14) 239次浏览 0个评论 扫描二维码

完整目录请见:Django 3网页开发指南 - 第4版

本章中包含如下小节:

  • 让表单免受跨站请求伪造(CSRF)攻击
  • 使用内容安全策略(CSP)让请求安全
  • 使用django-admin-honeypot
  • 实现密码校验
  • 下载授权文件
  • 对图片添加动态水印
  • 使用Auth0进行认证
  • 缓存方法返回值
  • 使用Memcached缓存Django视图
  • 使用Redis缓存Django视图

引言

如果软件不当地暴露敏感信息,让用户陷入漫长的等待或是消耗大量的硬件资源,那么就很难持久存在。开发者有责任保证应用的安全和高性能。本章中,我们将查看一些方式来让用户(以及你自己)在Django应用内进行操作时保持安全。然后,我们会讲解一些降低处理时间的缓存选择,并让获取用户数据在金钱和时间层面上都实现低开销。

技术要求

运行本章的代码要求安装最新稳定版的Python 3、MySQL或PostgreSQL数据库以及通过虚拟环境创建的Django项目。

可在GitHub仓库的Chapter07目录中查看本章的代码。

让表单免受跨站请求伪造(CSRF)攻击

不加以适当的预防措施,恶意站点可以对你的网站进行某些请求,进而对你的服务器进行一些预料外的修改。例如,可以影响到用户认证或在未经用户许可的情况下修改内容。Django自带有一套防止这类CSRF攻击的系统,本小节中我们就来进行了解。

准备工作

使用第3章 表单和视图使用CRUDL函数创建应用一节所创建的ideas应用。

如何实现…

按照如下步骤来启用Django中的CSRF防护:

  1. 确保在配置文件中包含了CsrfViewMiddleware,如下所示:
  2. 确保表单视图使用request上下文进行得渲染。例如,在已有的ideas应用中有如下内容:
  3. 在用于表单的模板中,应用使用POST方法并包含{% csrf_token %}标签:
  4. 如果在表单布局中使用了django-crispy-forms,默认会包含CSRF令牌:

实现原理…

Django使用隐藏字段来防止CSRF攻击。由服务端根据具体请求和随机信息生成一个令牌。再借由CsrfViewMiddleware自动让该令牌在请求上下文中可用。虽然不建议禁用这一中间件,但通过应用@csrf_protect装饰器也可以标记单独的视图来实现这一行为:

类似地,即使启用了这一中间件,我们也可以通过使用@csrf_exempt装饰器对单独视图排除CSRF检查:

内置的 {% csrf_token %} 标签生成一个带有令牌的隐藏input字段,如下例所示:

在使用GET、HEAD、OPTIONS或TRACE方法提交请求的表单中包含令牌视作无效,因此使用这些方法的请求首先不应产生副面效应。大多数情况下,要求进行CSRF保护的web表单使用POST请求。

受保护的表单使用不带所要求token进行提交时,Django的内置表单校验会识别出并彻底拒绝请求。仅在提交中包含具有有效值的token时才允许进行一步处理。其结果就是外部网站无法对你的服务端做出修改,因为它们无法知晓并包含当前有效的token值。

扩展知识…

在很多情况下,改善表单让其可通过Ajax进行提交会比较好。这时还需要使用CSRF令牌进行保护,可以在每个请求中以附加数据注入这个信息,使用这一方法要求开发者记住在每个POST请求中这么操作。另一种方法是使用 已有的CSRF令牌header,这样更为高效。

首先,令牌值需要主动获取,如何获取由CSRF_USE_SESSIONS配置值所决定。在值为True时,信息存储在session中而非cookie中,因此我们必须使用 {% csrf_token %}标签并在DOM中包含它。然后我们可以在JavaScript中读取该元素来获取这一数据:

CSRF_USE_SESSIONS配置默认是False状态,令牌值的获取数据源是csrftoken的cookie。虽然可以自己实现cookie操作方法,但有很多可用的工具可以简化这一过程。例如,我们可以使用js-cookie API来通过名称更新轻易地提取令牌,如下所示:

一旦提取出令牌,需要为XmlHttpRequest将其设置为CSRF令牌头部。虽然这可以通过每次请求分别来完成,但这样会和对每个请求添加请求参数数据存在同样的缺点。我们可以使用jQuery及其功能来在发送前自动对每个请求添加数据,如下:

相关内容

  • 第3章 表单和视图中的使用CRUDL函数创建应用一节
  • 实现密码校验一节
  • 下载授权文件一节
  • 使用Auth0进行认证一节

使用内容安全策略(CSP)让请求安全

动态多用户网站通常允许用户添加多种类型的媒体文件:图片、视频、音频、HTML、JavaScript代码段等等。这会让用户有可能通过对网站添加恶意代码来窃取cookie或个人信息、在后台调用计划外的Ajax请求或对其它用户产生不利。现代浏览器支持额外的安全层,对你的媒体资源来源设置白名单。这称之为CSP,在本节中,我们将展示如何在Django站点中使用它。

准备工作

我们使用已有的Django项目,比如第3章 表单和视图中包含有ideas应用的代码。

如何实现…

通过如下步骤来实现CSP的保护:

  1. 在你的虚拟环境中安装django-csp:
  2. 在配置文件中添加CSPMiddleware:
  3. 在同一配置文件中,添加django-csp配置用于对于你所信任的媒体来源设置白名单,例如jQuery和Bootstrap的CDN(在实现原理…一节中会进行详细讲解):
  4. 如果在模板中存在行内脚本或样式,使用加密的nonce对设置白名单,如下所示:

实现原理…

可以在head版块或响应头中添加CSP至meta标签中:

  • meta标签语法像下面这样:
  • 我们所选的 django-csp模块使用响应头创建希望在网站中加载的数据源列表。可以在浏览器检查器的Network标签中查看响应头,如下:

CSP允许我们定义资源类型并允许以彼此为数据源。可以使用的主要指令如下:

  • default-src用作所有未设置数据源的替补,由Django配置中的CSP_DEFAULT_SRC进行控制。
  • script-src用于<script> 标签,由Django配置中的CSP_SCRIPT_SRC进行控制。
  • style-src用于<style> 和 <link rel=”stylesheet”>标签以及CSS @import语句,由配置中的CSP_STYLE_SRC进行控制。
  • img-src用于<img> 标签,由配置中的CSP_IMG_SRC控制。
  • frame-src用于<frame> 和 <iframe>标签,由配置中的CSP_FRAME_SRC控制。
  • media-src用于<audio>、<video>和<track> 标签,由配置中的CSP_MEDIA_SRC控制。
  • font-src用于网页字体,由配置中的CSP_FONT_SRC控制。
  • connect-src用于由JavaScript所加载的资源,由配置中的CSP_CONNECT_SRC控制。

ℹ️ 完整的资源类型列表及对应的配置项分别参见CSP类型Django CSP配置

每条指令的值可为以下列表中的一个或多个(引号应保留):

  • *: 任意数据源
  • ‘none’: 禁止任意数据源
  • ‘self’: 允许来自相同域名的数据源
  • 某一协议,如https: 或data:
  • 某一域名,例如example.com 或 *.example.com
  • 一个网站URL,如https://example.com
  • ‘unsafe-inline’: 允许使用行内<script> 或 <style> 标签
  • ‘unsafe-eval’: 允许通过eval() 函数进行脚本执行
  • ‘nonce-<b64-value>’:  允许通过加密nonce后的具体标签
  • ‘sha256-…’: 允许通过数据源哈希的资源

没有什么绝对安全的通用django-csp配置方式。这是一个试错的过程。但以下我们提供一些纲领:

  1. 先对已运行项目添加CSP。过度的限制只会让开发网站变得过于困难。
  2. 查看硬编码至模板中的脚本、样式、字体和其它静态文件并设置白名单。
  3. 如果允许将媒体文件嵌入到博客文章或其它动态内容中的话允许所有的图片、媒体文件及frame的数据源,如下:
  4. 如果使用行内脚本或样式的话,对它们添加nonce=”{{ request.csp_nonce }}”。
  5. 避免使用CSP值’unsafe-inline’ 和 ‘unsafe-eval’,除非只能选择在模板中通过硬编码将HTML添加到网站中。
  6. 浏览整个网站并查看有哪些没有正确加载的内容。如果在开发者控制台中看到如下消息,则表示内容受到了CSP的限制:拒绝执行行内脚本,因为即违反了下述的内容安全策略指令:”script-src ‘self’ https://stackpath.bootstrapcdn.com/ https://code.jquery.com/ https://cdnjs.cloudflare.com/”。需要使用’unsafe-inline’关键词、哈希 (‘sha256- P1v4zceJ/oPr/yp20lBqDnqynDQhHf76lljlXUxt7NI=’)或nonce(‘nonce-…’) 来进行行内执行。

    这类错误通过发生在第三方工具(如django-cms、Django调试工具栏或谷歌分析)尝试通过JavaScript包含某一资源时。可以通过像在上面错误消息中所看到的数据源哈希’sha256-P1v4zceJ/oPr/yp20lBqDnqynDQhHf76lljlXUxt7NI=’.那样来对这些资源添加白名单。
  7. 如果你在开发一个现代增强型网页应用(PWA),应检查下由CSP_MANIFEST_SRC和CSP_WORKER_SRC配置所控制的声明指令及网页worker。

相关内容

  • 让表单免受跨站请求伪造(CSRF)攻击一节

使用django-admin-honeypot

如果保留Django站点的默认后台访问路径,会存在让黑客进行暴力攻击以及使用他们列表中的不同密码进行登录尝试的风险。有一个名为django-admin-honeypot的应用,允许我们伪装登录页面并监测这些暴力攻击行为。本节中就学习如何来使用。

准备工作

可以使用任意想要进行安全保护的Django项目。例如,我们可以对前一小节中的项目进行扩展。

如何实现…

按照如下步骤来配置django-admin-honeypot:

  1. 在虚拟环境中安装该模块:
  2. 将admin_honeypot添加到配置文件的INSTALLED_APPS中:
  3. 修改URL规则:

实现原理…

此时如果访问默认的后台URLhttp://127.0.0.1:8000/en/admin/,可以看到如下登录页面,但不论输入什么内容都会返回无效密码:

TODO

现在真实的后台地址为ttp://127.0.0.1:8000/en/management/,其中可以通过蜜罐查看到所追踪的登录信息。

扩展知识…

在编写本书时,django-admin-honeypot 在Django 3.0下还无法完好运行,后台界面对HTML进行了转义,而其实应该进行安全渲染。在django-admin-honeypot发布更新版本之前,我们可以通过一些小修改来进行解决,如下:

  1. 通过admin.py文件创建名为admin_honeypot_fix的应用,代码如下:
  2. 同样在该应用中,使用如下新的应用配置创建apps.py文件:
  3. 在本文中的INSTALLED_APPS替换admin_honeypot为新的应用配置:

蜜罐捕获的登录尝试如下所示:

TODO

相关内容

  • 实现密码校验一节
  • 使用Auth0进行认证一节

实现密码校验

软件安全问题中排在前列的要属用户选择了不安全的密码。本节中,我们将学习如何通过内置及自定义的密码校验器来强制实现最低密码的要求,这样用户会被引导设置更为安全的密码。

准备工作

打开项目的配置文件定位到AUTH_PASSWORD_VALIDATORS配置项。同时新建一个auth_extra应用并添加password_validation.py 文件。

如何实现…

按照如下步骤来为项目设置等级更强的密码:

  1. 自定义校验器的Django配置,添加如下选项:
  2. 在新的auth_extra应用的password_validation.py文件中添加MaximumLengthValidator类,如下所示:
  3. 同样在该文件中创建SpecialCharacterInclusionValidator类:
  4. 然后在配置中新增校验器:

实现原理…

Django中包含很多默认的密码校验器:

  • UserAttributeSimilarityValidator确保所选择的密码与用户的一些属性不具备相似性。默认,相似度设置为0.7,所检测的属性有用户名、姓氏和email地址。如果这此属性包含多个单词,则分别对每个单词进行检测。
  • MinimumLengthValidator检查所输入的密码不小于最小字符数。默认,密码必须大于等于8个字符。
  • CommonPasswordValidator指一个包含经常使用密码的列表,因此不够安全。Django所使用的默认列表中含有1000个这类密码。
  • NumericPasswordValidator验证所输入密码并非全是数字。

在使用startproject管理命令新建项目时,会使用默认选项添加它们为初始校验器。本节中,我们将展示如何按我们的项目需要调整这些选项,将密码最小长度提升为12个字符。

对于UserAttributeSimilarityValidator,我们将还将max_similarity减到了0.5,这表示密码要比默认与这些用户属性具有更大的差别。

在password_validation.py中,我们定义了两个新的校验器:

  • MaximumLengthValidator非常类似于内置的最小长度校验器,保证密码长度默认不超过24个字符
  • SpecialCharacterInclusionValidator检测在给定的密码中是否包含一个或多个特殊字符,默认有$、%、:、#和!

每个校验器类必须要有两个方法:

  • validate()方法对password参数执行实际的检测。在用户进行认证后可传递第二个可选参数user。
  • 还应提供一个get_help_text()方法,返回描述用户验证要求的字符串。

最后,我们在配置中添加了一个新验证器,用于重写默认项为密码最大长度为32个字符,并在默认特殊字符列表中增加符号{、}、^和&。

扩展知识…

AUTH_PASSWORD_VALIDATORS中所提供的验证器在执行createsuperuser和changepassword管理命令以及用内置表单修改或重置密码时会自动执行。但有时会希望对自定义的密码管理命令执行同样的验证。Django为这一级别的集成提供了一些函数,可在django.contrib.auth.password_validation模块中社区贡献的Django auth应用中查看详情。

相关内容

  • 下载授权文件一节
  • 使用Auth0进行认证一节

下载授权文件

有时可能希望只有指定人员下载网站上具有知识产权的内容。例如 ,音乐、视频、文学或其它艺术作品只允许付费会员访问。本节中,我们将学习如何使用社区的Django auth应用来仅允许授权的用户进行图片下载。

准备工作

我们使用第3章 表单和视图中所创建的ideas应用。

如何实现…

逐一执行如下步骤:

  1. 创建一个视图要求认证后才能下载文件,如下所示:
  2. 在URL配置文件中添加下载的视图:
  3. 在项目的URL配置文件中设置登录视图:
  4. 为登录表单创建一个模板,如以下代码所示:
  5. 在idea详情页模板中,添加一个下载链接:

应当限制用户绕过Django直接下载受限文件。如果运行 Apache 2.4,在Apache web 服务器上可以在media/ideas目录的.htaccess文件中添加如下内容:

ℹ️在使用django-imagekit时,本书中一直有使用,所生成的图片存储于media/CACHE目录中对外提供服务,因为我们的.htaccess配置不会对其产生影响。

实现原理…

 

相关内容

对图片添加动态水印

准备工作

如何实现…

实现原理…

相关内容

使用Auth0进行认证

准备工作

如何实现…

实现原理…

相关内容

缓存方法返回值

准备工作

如何实现…

实现原理…

扩展知识…

相关内容

使用Memcached缓存Django视图

准备工作

如何实现…

实现原理…

相关内容

使用Redis缓存Django视图

准备工作

如何实现…

实现原理…

扩展知识…

相关内容

喜欢 (0)
[]
分享 (0)
发表我的评论
取消评论

表情 贴图 加粗 删除线 居中 斜体 签到

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址