如何加强生产Django项目的安全性

来自菜鸟教程
跳转至:导航、​搜索

作者选择了 COVID-19 Relief Fund 作为 Write for DOnations 计划的一部分来接受捐赠。

介绍

开发 Django 应用程序可以是一种快速而干净的体验,因为它的方法灵活且可扩展。 Django 还提供了各种面向安全的设置,可以帮助您无缝地准备项目以进行生产。 但是,在生产部署方面,有多种方法可以进一步保护您的项目。 通过分解设置来重组项目将允许您根据环境轻松设置不同的配置。 利用 dotenv 隐藏环境变量或机密设置将确保您不会发布任何可能危及项目的细节。

虽然实施这些不同的策略和功能起初可能看起来很耗时,但开发一个实用的工作流程将允许您部署项目的版本,而不会影响安全性或您的生产力。

在本教程中,您将通过实现和配置基于环境的设置、dotenv 和 Django 的内置安全设置来利用面向安全的工作流进行 Django 开发。 这些功能都相互补充,将产生一个版本的 Django 项目,该版本可用于您可能采用的不同部署方法。

先决条件

在开始本指南之前,您需要以下内容:

注意:如果您使用的是现有的 Django 项目,您可能有不同的要求。 本教程建议了一个特定的项目结构,但是,您也可以根据需要单独使用本教程的每个部分。


第 1 步——重构 Django 的设置

在第一步中,您首先将 settings.py 文件重新排列为特定于环境的配置。 当您需要在不同环境(例如开发和生产)之间移动项目时,这是一个很好的做法。 这种安排意味着针对不同环境的更少重新配置; 相反,您将使用环境变量在配置之间切换,我们将在本教程后面讨论。

在项目的子目录中创建一个名为 settings 的新目录:

mkdir testsite/testsite/settings

(根据我们使用 testsite 的先决条件,但您可以在此处替换您的项目名称。)

此目录将替换您当前的 settings.py 配置文件; 您所有基于环境的设置都将位于此文件夹中包含的单独文件中。

在新的 settings 文件夹中,创建三个 Python 文件:

cd testsite/testsite/settings
touch base.py development.py production.py

development.py 文件将包含您在开发过程中通常使用的设置。 production.py 将包含在生产服务器上使用的设置。 您应该将它们分开,因为生产配置将使用在开发环境中不起作用的设置; 例如,强制使用 HTTPS、添加标头和使用生产数据库。

base.py 设置文件将包含 development.pyproduction.py 将继承的设置。 这是为了减少冗余并帮助保持代码更干净。 这些 Python 文件将替换 settings.py,因此您现在将删除 settings.py 以避免混淆 Django。

仍在 settings 目录中时,使用以下命令将 settings.py 重命名为 base.py

mv ../settings.py base.py

您刚刚完成了新的基于环境的设置目录的大纲。 您的项目尚不了解您的新配置,因此接下来,您将解决此问题。

第 2 步 — 使用 python-dotenv

目前 Django 将无法识别您的新设置目录或其内部文件。 因此,在继续使用基于环境的设置之前,您需要让 Django 使用 python-dotenv。 这是一个从 .env 文件加载环境变量的依赖项。 这意味着 Django 将查看项目根目录中的 .env 文件,以确定它将使用哪些设置配置。

转到项目的根目录:

cd ../../

安装 python-dotenv

pip install python-dotenv

现在您需要配置 Django 以使用 dotenv。 为此,您将编辑两个文件:用于开发的 manage.py 和用于生产的 wsgi.py

让我们从编辑 manage.py 开始:

nano manage.py

添加以下代码:

测试站点/manage.py

import os
import sys
import dotenv

def main():
    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'testsite.settings.development')

    if os.getenv('DJANGO_SETTINGS_MODULE'):
    os.environ['DJANGO_SETTINGS_MODULE'] = os.getenv('DJANGO_SETTINGS_MODULE')

    try:
        from django.core.management import execute_from_command_line
    except ImportError as exc:
        raise ImportError(
            "Couldn't import Django. Are you sure it's installed and "
            "available on your PYTHONPATH environment variable? Did you "
            "forget to activate a virtual environment?"
        ) from exc
    execute_from_command_line(sys.argv)


if __name__ == '__main__':
    main()

dotenv.load_dotenv(
    os.path.join(os.path.dirname(__file__), '.env')
)

保存并关闭manage.py,然后打开wsgi.py进行编辑:

nano testsite/wsgi.py

添加以下突出显示的行:

测试站点/testsite/wsgi.py

import os
import dotenv

from django.core.wsgi import get_wsgi_application

dotenv.load_dotenv(
    os.path.join(os.path.dirname(os.path.dirname(__file__)), '.env')
)

os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'testsite.settings.development')

if os.getenv('DJANGO_SETTINGS_MODULE'):
 os.environ['DJANGO_SETTINGS_MODULE'] = os.getenv('DJANGO_SETTINGS_MODULE')

application = get_wsgi_application()

您添加到这两个文件中的代码做了两件事。 首先,每当 Django 运行时——manage.py 用于运行开发,wsgi.py 用于生产——你告诉它寻找你的 .env 文件。 如果该文件存在,则指示 Django 使用 .env 推荐的设置文件,否则默认使用开发配置。

保存并关闭文件。

最后,让我们在项目的根目录中创建一个 .env

nano .env

现在添加以下行以将环境设置为开发:

测试站点/.env

DJANGO_SETTINGS_MODULE="testsite.settings.development"

注意:.env 添加到您的 .gitignore 文件中,这样它就不会包含在您的提交中; 您将使用此文件来包含您不希望公开显示的密码和 API 密钥等数据。 您的项目运行的每个环境都有自己的 .env 以及针对该特定环境的设置。

建议创建一个 .env.example 以包含在您的项目中,这样您就可以在任何需要的地方轻松创建一个新的 .env


因此,默认情况下 Django 将使用 testsite.settings.development,但如果您将 DJANGO_SETTINGS_MODULE 更改为 testsite.settings.production,它将开始使用您的生产配置。 接下来,您将填充 development.pyproduction.py 设置配置。

第 3 步 - 创建开发和生产设置

接下来,您将打开 base.py 并在单独的 development.pyproduction.py 文件中添加要为每个环境修改的配置。 production.py 将需要使用您的生产数据库凭据,因此请确保您拥有这些凭据。

注意: 您可以根据环境确定需要配置哪些设置。 本教程将仅介绍生产和开发设置的常见示例(即安全设置和单独的数据库配置)。


在本教程中,我们使用 先决条件教程 中的 Django 项目作为示例项目。 我们会将设置从 base.py 移动到 development.py。 首先打开 development.py

nano testsite/settings/development.py

首先,您将从 base.py 导入 - 此文件继承 base.py 的设置。 然后,您将传输要为开发环境修改的设置:

测试站点/测试站点/设置/development.py

from .base import *

DEBUG = True

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
    }
}

在这种情况下,特定于开发的设置是:DEBUG,你在开发中需要这个True,但在生产中不需要; 和 DATABASES,本地数据库而不是生产数据库。 您在这里使用 SQLite 数据库进行开发。

注意: 出于安全考虑,Django 的 DEBUG 输出永远不会显示任何可能包含以下字符串的设置:APIKEYPASSSECRETSIGNATURETOKEN

这是为了确保不会泄露机密,如果您不小心将项目部署到生产环境中并启用了 DEBUG

话虽如此,永远不要在启用 DEBUG 的情况下公开部署项目。 它只会使您的项目的安全性处于危险之中。


接下来,让我们添加到 production.py

nano testsite/settings/production.py

生产将类似于 development.py,但具有不同的数据库配置和 DEBUG 设置为 False

测试站点/测试站点/设置/production.py

from .base import *

DEBUG = False

ALLOWED_HOSTS = []

DATABASES = {
    'default': {
        'ENGINE': os.environ.get('SQL_ENGINE', 'django.db.backends.sqlite3'),
        'NAME': os.environ.get('SQL_DATABASE', os.path.join(BASE_DIR, 'db.sqlite3')),
        'USER': os.environ.get('SQL_USER', 'user'),
        'PASSWORD': os.environ.get('SQL_PASSWORD', 'password'),
        'HOST': os.environ.get('SQL_HOST', 'localhost'),
        'PORT': os.environ.get('SQL_PORT', ''),
    }
}

对于给出的示例数据库配置,您可以使用 dotenv 来配置每个给定的凭据,包括默认值。 假设您已经为项目的生产版本设置了数据库,请使用您的配置而不是提供的示例。

您现在已将项目配置为使用基于 .env 中的 DJANGO_SETTINGS_MODULE 的不同设置。 鉴于您使用的示例设置,当您将项目设置为使用生产设置时,DEBUG 变为 FalseALLOWED_HOSTS 被定义,并且您开始使用不同的数据库您已经(已经)在您的服务器上进行了配置。

第 4 步 - 使用 Django 的安全设置

Django 包含可供您添加到项目中的安全设置。 在此步骤中,您将向项目添加对任何生产项目都至关重要的安全设置。 这些设置适用于您的项目向公众开放时使用。 不建议在您的开发环境中使用任何这些设置; 因此,在此步骤中,您将这些设置限制为 production.py 配置。

在大多数情况下,这些设置将强制对各种 Web 功能使用 HTTPS,例如 会话 cookie 、CSRF cookie、将 HTTP 升级到 HTTPS 等等。 因此,如果您尚未使用指向它的域设置您的服务器,请暂时搁置此部分。 如果您需要为部署准备好服务器,请查看 结论 以获取有关此内容的建议文章。

先打开production.py

nano production.py

根据代码后面的说明,在您的文件中添加适用于您的项目的突出显示设置:

测试站点/测试站点/设置/production.py

from .base import *

DEBUG = False

ALLOWED_HOSTS = ['your_domain', 'www.your_domain']

DATABASES = {
    'default': {
        'ENGINE': os.environ.get('SQL_ENGINE', 'django.db.backends.sqlite3'),
        'NAME': os.environ.get('SQL_DATABASE', os.path.join(BASE_DIR, 'db.sqlite3')),
        'USER': os.environ.get('SQL_USER', 'user'),
        'PASSWORD': os.environ.get('SQL_PASSWORD', 'password'),
        'HOST': os.environ.get('SQL_HOST', 'localhost'),
        'PORT': os.environ.get('SQL_PORT', ''),
    }
}

SECURE_SSL_REDIRECT = True

SESSION_COOKIE_SECURE = True

CSRF_COOKIE_SECURE = True

SECURE_BROWSER_XSS_FILTER = True
  • SECURE_SSL_REDIRECT 将所有 HTTP 请求重定向到 HTTPS(除非豁免)。 这意味着您的项目将始终尝试使用加密连接。 您需要在服务器上配置 SSL 才能使其正常工作。 请注意,如果您已将 Nginx 或 Apache 配置为执行此操作,则此设置将是多余的。
  • SESSION_COOKIE_SECURE 告诉浏览器 cookie 只能通过 HTTPS 处理。 这意味着您的项目为活动(例如登录)生成的 cookie 只能通过加密连接工作。
  • CSRF_COOKIE_SECURESESSION_COOKIE_SECURE 相同,但适用于您的 CSRF 令牌。 CSRF 令牌 防止 跨站点请求伪造。 Django CSRF 保护通过确保提交给您的项目的任何表单(用于登录、注册等)是由您的项目而不是第三方创建的。
  • SECURE_BROWSER_XSS_FILTER 在所有没有的响应上设置 X-XSS-Protection: 1; mode=block 标头。 这可确保第三方无法将脚本注入您的项目。 例如,如果用户使用公共字段将脚本存储在您的数据库中,则当该脚本被检索并显示给其他用户时,它将不会运行。

如果您想了解更多关于 Django 中可用的不同安全设置的信息,请查看他们的 文档

警告: Django 的文档指出你不应该完全依赖 SECURE_BROWSER_XSS_FILTER。 永远不要忘记验证和清理输入。


其他设置

以下设置用于支持 HTTP 严格传输安全 (HSTS) — 这意味着您的整个站点必须始终使用 SSL。

  • SECURE_HSTS_SECONDS 是 HSTS 设置的时间量(以秒为单位)。 如果您将此设置为一小时(以秒为单位),则每次您访问网站上的网页时,它都会告诉您的浏览器在接下来的一小时内,HTTPS 是您访问该网站的唯一方式。 如果在那一小时内您访问了网站的不安全部分,浏览器将显示错误并且不安全的页面将无法访问。
  • SECURE_HSTS_PRELOAD 仅在设置了 SECURE_HSTS_SECONDS 时有效。 此标头指示浏览器 预加载 您的站点。 这意味着您的网站将被添加到硬编码列表中,该列表在 Firefox 和 Chrome 等流行浏览器中实现。 这要求您的网站始终加密。 小心这个标题很重要。 如果您在任何时候决定不为您的项目使用加密,则可能需要数周时间才能从 HSTS 预加载列表 中手动删除。
  • SECURE_HSTS_INCLUDE_SUBDOMAINS 将 HSTS 标头应用于所有子域。 启用此标头意味着 your_domainunsecure.your_domain 都需要加密,即使 unsecure.your_domain 与此 Django 项目无关。

警告: 错误地配置这些附加设置可能会在很长一段时间内破坏您的站点。

在实施这些设置之前,请阅读 HSTS 上的 Django 文档。


有必要考虑这些设置将如何与您自己的 Django 项目一起使用; 总的来说,这里讨论的设置是大多数 Django 项目的良好基础。 接下来,您将回顾 dotENV 的一些进一步用法。

第 5 步 — 使用 python-dotenv 作为秘密

本教程的最后一部分将帮助您利用 python-dotenv。 这将允许您隐藏某些信息,例如项目的 SECRET_KEY 或管理员的登录 URL。 如果您打算在 GitHub 或 GitLab 等平台上发布代码,这是一个好主意,因为这些秘密不会被发布。 相反,每当您最初在本地环境或服务器上设置项目时,您都可以创建一个新的 .env 文件并定义这些秘密变量。

您必须隐藏您的 SECRET_KEY,以便在本节中开始处理。

打开您的 .env 文件:

nano .env

并添加以下行:

测试站点/.env

DJANGO_SETTINGS_MODULE="django_hardening.settings.development"
SECRET_KEY="your_secret_key"

在你的 base.py 中:

nano testsite/settings/base.py

让我们像这样更新 SECRET_KEY 变量:

测试站点/测试站点/设置/base.py

. . .
SECRET_KEY = os.getenv('SECRET_KEY')
. . .

您的项目现在将使用位于 .env 中的 SECRET_KEY

最后,您将通过向其中添加一长串随机字符来隐藏您的管理员 URL。 这将确保机器人无法暴力破解登录字段,并且陌生人无法尝试猜测登录名。

再次打开.env

nano .env

并添加一个 SECRET_ADMIN_URL 变量:

测试站点/.env

DJANGO_SETTINGS_MODULE="django_hardening.settings.development"
SECRET_KEY="your_secret_key"
SECRET_ADMIN_URL="very_secret_url"

现在让我们告诉 Django 用 SECRET_ADMIN_URL 隐藏你的管理 URL:

nano /testsite/urls.py

注意:不要忘记用你自己的秘密字符串替换your_secret_keyvery_secret_url。 另外不要忘记将 very_secret_url 替换为您自己的秘密 URL。

如果你想为这些变量使用随机字符串,Python 提供了一个很棒的 secrets.py 库 来生成这样的字符串。 他们提供的 示例 是创建用于生成安全随机字符串的小型 Python 程序的好方法。


像这样编辑管理 URL:

测试站点/测试站点/urls.py

import os
from django.contrib import admin
from django.urls import path

urlpatterns = [
    path(os.getenv('SECRET_ADMIN_URL') + '/admin/', admin.site.urls),
]

您现在可以在 very_secret_url/admin/ 找到管理员登录页面,而不仅仅是 /admin/

结论

在本教程中,您已经配置了当前的 Django 项目,以便在不同的环境中轻松使用。 您的项目现在利用 python-dotenv 来处理机密和设置。 您的生产设置现在启用了 Django 的内置安全功能。

如果您已按照指示启用了所有推荐的安全组件并重新实施了设置,那么您的项目将具有以下主要功能:

  • 用于所有通信的 SSL/HTTPS(例如,子域、cookie、CSRF)。
  • XSS(跨站点脚本)攻击预防。
  • CSRF(跨站点请求伪造)攻击预防。
  • 隐藏的项目密钥。
  • 隐藏的管理员登录 URL,防止暴力攻击。
  • 开发和生产的单独设置。

如果您有兴趣了解有关 Django 的更多信息,请查看我们关于 Django 开发 的教程系列。

此外,如果您还没有将项目投入生产,这里有一个关于 如何在 Ubuntu 20.04 上使用 Postgres、Nginx 和 Gunicorn 设置 Django 的教程。 您还可以查看我们的 Django 主题页面 以获取更多教程。

当然,请阅读 Django 的 设置文档 ,了解更多信息。