Django 开发环境的 HTTPS

某些现代网站功能需要 HTTPS,包括渐进式 Web 应用 (PWA)、WebSockets、摄像头和麦克风使用以及地理位置检测。但是,默认的 Django runserver 命令只能在 HTTP 下运行 Web 服务器。本地开发环境中可以使用 HTTPS 吗?当然可以,在本文中,我将指导您如何设置它。

TLS/SSL 证书的工作原理

首先,让我们对 HTTPS 的工作原理有一个共识。

HTTPS 可以保证网站的安全,保护传输的访客的数据(如密码或信用卡信息)。

**TLS(传输层安全性)**取代了**SSL(安全套接字层)**,它是一种在后台运行的安全工具,通过加密交换的数据来创建客户端(如浏览器)和服务器之间的安全连接。

它们使用公钥(通过证书共享)和私钥(由服务器保密)来建立加密连接。这些密钥通常存储在扩展名为“.pem”的“BASE64”编码文件中。

**证书颁发机构 (CA)** 是负责颁发和管理 TLS/SSL 加密中使用的数字证书的受信任组织或实体。受信任的证书颁发机构会验证网站所有者的身份并签署其证书以确保其真实性。

受信任的证书颁发机构会签署证书以验证服务器的身份,客户端(如浏览器)会根据证书颁发机构的根证书对其进行验证,以确保连接真实且安全。CA 根证书通常存储在操作系统或浏览器中,并会定期通过软件更新进行更新。

一些著名的证书颁发机构包括:

  • Let's Encrypt(免费、自动化证书),
  • DigiCert,
  • GlobalSign,
  • Sectigo (以前称为 Comodo)。
  • 对于本地开发,可以使用简单的工具 **mkcert** 创建本地受信任的 TLS 证书,以保护开发环境,而无需外部证书颁发机构。很快,您就会看到如何做到这一点。

    TLS/SSL 证书不仅用于 HTTPS 连接,还用于保护其他通信协议,包括 WebSockets(`wss://`)、FTPS、数据库连接、电子邮件协议(SMTP、IMAP、POP3)和消息代理(例如 Redis、RabbitMQ、Kafka)。

    1. /etc/hosts 文件

    我通常为我的开发环境创建本地主机名,以便通过域访问它们,例如“djangotricks.local”而不是“localhost”或“127.0.0.1”。这可以通过在“/etc/hosts”文件中添加映射行来实现:

    127.0.0.1 djangotricks.local

    2. 安装 mkcert

    让我们将 **mkcert** 安装到您的开发环境中。我在 macOS 上使用 Homebrew 和以下命令:

    $ brew install mkcert
    $ brew install nss  # for Firefox
    $ mkcert -install

    其他操作系统的安装说明可在 处找到。

    3. 为本地域创建证书

    我在我的 Django 项目中创建了一个目录“certs”,将当前目录更改为该目录,然后运行:

    mkcert djangotricks.local

    之后,我在其中创建了 `.gitignore` 文件,其中包含以下内容,该文件忽略除其自身之外的所有内容:

    *
    !.gitignore

    最后,我的项目的文件结构如下所示:

    djangotricks/
    ├── certs/
    │   ├── .gitignore
    │   ├── djangotricks.local.pem  - public certificate
    │   ├── djangotricks.local-key.pem  - private key
    ├── manage.py

    4.Django 设置

    我在开发环境设置文件“djangotricks/settings/dev.py”中添加了一些与 TLS 相关的设置:

    CSRF_TRUSTED_ORIGINS = [
        "https://djangotricks.local:8000",
        "http://djangotricks.local:8000",
    ]
    
    CSRF_COOKIE_SECURE = True
    SESSION_COOKIE_SECURE = True

    5.a)为 WSGI 服务器创建自定义脚本

    下一步是准备在 HTTPS 下运行 Web 服务器的脚本。经过与 Claude AI 几次沟通后,我创建了一个文件“run_https_wsgi_server.py”,内容如下:

    import os
    import ssl
    from pathlib import Path
    from django.core.servers.basehttp import get_internal_wsgi_application
    from wsgiref.simple_server import make_server
    from watchfiles import run_process
    
    def run_server():
        # Set Django environment
        os.environ.setdefault("DJANGO_SETTINGS_MODULE", "djangotricks.settings.dev")
        application = get_internal_wsgi_application()
    
        # Set certificate paths
        cert_path = Path(__file__).parent / "certs"
        ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
        ssl_context.load_cert_chain(
            certfile=cert_path / "djangotricks.local.pem",
            keyfile=cert_path / "djangotricks.local-key.pem",
        )
    
        # Run server with TLS
        with make_server("djangotricks.local", 8000, application) as httpd:
            httpd.socket = ssl_context.wrap_socket(httpd.socket, server_side=True)
            print("Serving on https://djangotricks.local:8000")
            try:
                httpd.serve_forever()
            except KeyboardInterrupt:
                print("Server stopped.")
    
    if __name__ == "__main__":
        # Watch directories for changes
        watched_dirs = [
            str(Path(__file__).parent), # Current directory
        ]
    
        # Start watching and run the server with auto-reload
        run_process(
            *watched_dirs,            # Paths to watch
            target=run_server,        # Function to run
            debounce=1600,            # Debounce changes (milliseconds)
            recursive=True,           # Watch directories recursively
            debug=True                # Enable debug logging
        )

    该 Python 脚本使用 HTTPS 下的 Django WSGI 运行 Python 的默认 Web 服务器,监视项目文件的变化,并在有更新时重新启动服务器。

    然后我安装了依赖项“watchfiles”,这是一个由 Rust 提供支持的文件更改观察程序:

    (venv)$ pip install watchfiles

    这样我就可以使用以下命令运行 Web 服务器脚本:

    (venv)$ python run_https_wsgi_server.py

    5.b)为 ASGI 服务器创建自定义脚本

    类似地,我创建了一个脚本,用于在 HTTPS 下运行具有 ASGI 支持的 Web 服务器。它使用 **uvicorn** 作为 ASGI 协议,使用 **watchfiles** 自动重新加载:

    (venv)$ pip install uvicorn
    (venv)$ pip install watchfiles

    我将脚本命名为“run_https_asgi_server.py”,其内容如下:

    import os
    from pathlib import Path
    import uvicorn
    
    if __name__ == "__main__":
        # Set Django environment
        os.environ.setdefault("DJANGO_SETTINGS_MODULE", "djangotricks.settings.dev")
    
        # Set certificate paths
        project_root = Path(__file__).parent
        ssl_keyfile = project_root / "certs" / "djangotricks.local-key.pem"
        ssl_certfile = project_root / "certs" / "djangotricks.local.pem"
    
        # Run server with TLS
        uvicorn.run(
            "djangotricks.asgi:application",
            host="djangotricks.local",
            port=8000,
            ssl_keyfile=str(ssl_keyfile),
            ssl_certfile=str(ssl_certfile),
            reload=True,  # Enable autoreload
            reload_dirs=[
                str(project_root),
            ],
            reload_includes=["*.py", "*.html", "*.js", "*.css", "*.txt"],
            workers=1,
        )

    Uvicorn 的核心功能包括集成“watchfiles”和 TLS/SSL 支持。开发人员可以定义要监视哪些目录和文件类型以进行自动加载。

    最后,我可以使用以下命令运行该脚本:

    (venv)$ python run_https_asgi_server.py

    6. 测试你的服务器

    为了测试我的结果,我导航到 `https://djangotricks.local:8000`,浏览所有主页,尝试提交表单,尝试异步视图,尝试现代 JavaScript 功能之一,并尝试更改一些 HTML 标记以查看 Web 服务器如何自动重启以使更改立即生效。

    结论

    HTTPS 是现代 Web 开发的基本功能,在开发环境中设置它是绝对必要的。

    像“mkcert”这样的工具使得在本地使用 TLS 证书成为可能。

    尽管 Django 的“runserver”尚不支持 TLS/SSL 证书,但您可以创建自定义 Python(或 Bash)脚本来使用 HTTPS 和自动加载运行 Web 服务器。此外,如果您不需要自动加载,也可以很轻松地将其删除。

    现在,我该更新所有本地开发环境了。你也要更新吗?