bigywater.tech
Django · 127

HSTS 配置与 HTTPS 部署

HSTS 只是 HTTPS 部署的一小部分。更常见的事故是:Django 不知道自己在 HTTPS 后面。这篇模拟 TLS 终止代理,把 SECURE_PROXY_SSL_HEADER 的作用、被伪造的风险、以及 SECURE_SSL_REDIRECT 造成的重定向循环跑出来,再用真实的 TLS(自签名证书)验证 HSTS 头。

is_secure() = False
不配 SECURE_PROXY_SSL_HEADER,代理后面的 Django 永远认为是 http
头可被伪造
直连 Django 时带 X-Forwarded-Proto: https 就能让 is_secure() 为 True
301 循环
SECURE_SSL_REDIRECT + 代理始终用 http 转发 + 没配代理头

Django 怎么知道请求是 HTTPS

实测 · 本机执行输出
  [没有配置 SECURE_PROXY_SSL_HEADER]
  没有代理头                      → is_secure=False scheme=http
  X-Forwarded-Proto: https   → is_secure=False scheme=http
  X-Forwarded-Proto: http    → is_secure=False scheme=http
  [SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')]
  没有代理头                      → is_secure=False scheme=http
  X-Forwarded-Proto: https   → is_secure=True scheme=https
  X-Forwarded-Proto: http    → is_secure=False scheme=http
  ↑ 注意第二行:头是谁发的 Django 不知道。如果客户端能直连 Django(或者代理不清除这个头),任何人都能自称 https。

request.is_secure() 影响很多东西:安全 cookie、CSRF 的 Origin 检查(第 123 篇)、build_absolute_uri 的协议、HSTS 头。

重定向循环

实测 · 本机执行输出
  第 1 次(代理每次都用 http 转发给 Django)→ 301 https://localhost/
  第 2 次(代理每次都用 http 转发给 Django)→ 301 https://localhost/
  第 3 次(代理每次都用 http 转发给 Django)→ 301 https://localhost/
  第 4 次(代理每次都用 http 转发给 Django)→ 301 https://localhost/
  第 5 次(代理每次都用 http 转发给 Django)→ 301 https://localhost/
  浏览器跟随 Location 再请求,代理仍然以 http 转给 Django,所以永远是 301:重定向循环
  配置了代理头,代理带上 X-Forwarded-Proto: https → 200 is_secure=True scheme=https

如果代理本身已经做了 http → https 重定向,Django 这边就不必再开 SECURE_SSL_REDIRECT(这是设计建议,本文没有测)。

真实 TLS

实测 · 本机执行输出
  HTTPS GET / → 200 is_secure=True scheme=https  | Strict-Transport-Security: max-age=31536000; includeSubDomains
  明文 HTTP 发到同一个 TLS 端口 → RemoteDisconnected

  没有验证的事:浏览器收到 HSTS 之后的行为(记住 max-age、自动把 http 升级成 https、证书错误时不能点击继续),这些发生在浏览器里。

分步演示

HTTPS 与代理
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 / Python 3.14.7 / Django 6.1.1(默认用 SQLite,其它数据库会在文中注明),用 django.test.Client 或真实的本机服务器进程实测;计时用 time.perf_counter,数字每次运行会有小幅抖动。代理用测试客户端加 X-Forwarded-Proto 头模拟(没有真起 nginx);TLS 部分用 gunicorn --certfile / --keyfile 与 openssl 生成的自签名证书,客户端不校验证书
    • 没有验证:真实反向代理(nginx / Caddy)的头处理、HSTS 在浏览器里的行为、Let’s Encrypt 证书、SECURE_SSL_HOST、SECURE_REDIRECT_EXEMPT 与健康检查的配合(见第 136 篇)
    京ICP备2026067502号-1京公网安备11010502063487号