揭秘django-csp的Nonce机制:从request.csp_nonce到{% script %}模板标签,安全使用内联脚本
2026/8/25 8:26:06 网站建设 项目流程

揭秘django-csp的Nonce机制:从request.csp_nonce到{% script %}模板标签,安全使用内联脚本

【免费下载链接】django-cspContent Security Policy for Django.项目地址: https://gitcode.com/gh_mirrors/dj/django-csp

🛡️django-csp是 Django 生态中用于实现CSP(Content Security Policy,内容安全策略)的主流中间件,而CSP Nonce(一次性随机令牌)正是它应对“内联脚本”这一 XSS 重灾区的核心武器。本文将带你快速理解request.csp_nonce的生成原理、{% script %}模板标签的用法,以及如何安全地写内联 JavaScript。

为什么需要 Nonce?

如果你的 CSP 策略里写了'unsafe-inline',那内联脚本是安全的——但XSS 攻击也就完全放行了

Nonce 的巧妙之处在于:

  • ✅ 每个请求生成一个128 位随机值(Base64 编码),写入响应头
  • ✅ 只有带上该 nonce 的<script>标签才会被执行
  • ✅ 攻击者无法预测 nonce,即使注入成功,脚本也会被浏览器拦截

💡 在 django-csp 中,nonce 由 csp/middleware.py 里的_make_nonce方法生成,每个请求只会生成一次,后续读取都复用同一个值。

第一步:配置 NONCE 哨兵值

django-csp 4.x 采用“哨兵值”方式:在需要的指令里加入NONCE常量,nonce 才会出现在响应头中。

# settings.py from csp.constants import NONCE, SELF CONTENT_SECURITY_POLICY = { "DIRECTIVES": { "default-src": [SELF], "script-src": [SELF, NONCE], # 允许带 nonce 的内联脚本 "style-src": [SELF, NONCE], "frame-ancestors": [SELF], "report-uri": "/csp-report/", }, }

⚠️关键机制:只有当代码在响应处理完成之前真正读取了 nonce(例如str(request.csp_nonce)),响应头中才会包含'nonce-xxx'。没用到就不生成——这是 django-csp 的“懒生成”设计,源码见 csp/middleware.py 的process_response

三种方式使用 request.csp_nonce

方式一:直接在模板中引用

安装中间件后,request对象上会自动挂一个惰性属性csp_nonce

<script nonce="{{request.csp_nonce}}"> var hello = "world"; </script>

方式二:Context Processor 全局变量

如果页面里有大量<script>标签,可以把csp.context_processors.nonce加入context_processors,模板中直接使用{{CSP_NONCE}},省去每个标签都写request.csp_nonce的麻烦。实现见 csp/context_processors.py。

方式三:{% script %} 模板标签(推荐 ⭐)

人总会忘写nonce属性!django-csp 提供了{% script %}标签,每次渲染自动注入正确的 nonce,永远不会漏。

1. 注册模板库(settings.py):

TEMPLATES = [{ "OPTIONS": { "libraries": { "csp": "csp.templatetags.csp", } } }]

2. 在模板中使用

{% load csp %} {% script type="application/javascript" async=False %} <script> var hello = 'world'; </script> {% endscript %}

渲染结果(nonce 自动注入,嵌套的<script>标签会被剥掉):

<script nonce='123456' type="application/javascript" async=false>var hello='world';</script>

✨ 小技巧:标签体内用<script>包裹只是为了语法高亮,渲染时会被自动移除。还支持srciddeferintegrity等属性透传;带src时正文会被忽略。实现见 csp/templatetags/csp.py 和 csp/utils.py。

Jinja2 用户:在 TEMPLATES 的extensions中加入csp.extensions.NoncedScript即可使用完全相同的{% script %}语法,实现见 csp/extensions/init.py。

Nonce 机制的 3 个安全细节

细节说明
🔄懒生成未读取str(request.csp_nonce)前不产生随机值,bool(request.csp_nonce)始终安全可用
🚫响应后读取会报错中间件写完响应头后,再读 nonce 会抛出CSPNonceError,帮你抓住“nonce 没进头部”的编程错误
🔍开发者工具里看不到出于安全考虑,浏览器 DevTools 中不显示 nonce,请用 “查看源代码”(View Source)验证

如果你需要在响应前主动初始化 nonce(比如自定义中间件要提前使用),可以在MIDDLEWARECSPMiddleware之后加一段中间件执行str(getattr(request, "csp_nonce", None))

完整接入清单 📋

  1. 安装中间件MIDDLEWARE中加入"csp.middleware.CSPMiddleware"
  2. 配置策略CONTENT_SECURITY_POLICY中给script-src加上NONCE
  3. 模板改造:用{% script %}标签(或request.csp_nonce)替换所有手写内联脚本
  4. 上线验证:查看源代码确认<script nonce="...">与响应头'nonce-...'一致

🎉 至此,你就可以在不使用'unsafe-inline'的前提下安全运行内联脚本,把 XSS 攻击面再收窄一圈。更多配置细节可参考项目文档 docs/nonce.rst 与 docs/configuration.rst。

【免费下载链接】django-cspContent Security Policy for Django.项目地址: https://gitcode.com/gh_mirrors/dj/django-csp

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询