揭秘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>包裹只是为了语法高亮,渲染时会被自动移除。还支持src、id、defer、integrity等属性透传;带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(比如自定义中间件要提前使用),可以在MIDDLEWARE中CSPMiddleware之后加一段中间件执行str(getattr(request, "csp_nonce", None))。
完整接入清单 📋
- 安装中间件:
MIDDLEWARE中加入"csp.middleware.CSPMiddleware" - 配置策略:
CONTENT_SECURITY_POLICY中给script-src加上NONCE - 模板改造:用
{% script %}标签(或request.csp_nonce)替换所有手写内联脚本 - 上线验证:查看源代码确认
<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),仅供参考