Jupyter Notebook 默认拒绝跨域请求是因为其默认 Content-Security-Policy 中的 frame-ancestors 'self' 指令阻止 iframe 嵌入及 API 调用,需同时配置 c.NotebookApp.allow_origin 和 tornado_settings.headers['Content-Security-Policy'] 或通过 Nginx 显式设置 CSP 头。

为什么 jupyter notebook 默认拒绝跨域请求?
因为 Jupyter Notebook 启动时默认设置了严格的 Content-Security-Policy 头,其中包含 frame-ancestors 'self',这会直接拦截所有来自其他域名的 <iframe> 嵌入,也会限制前端通过 fetch 或 XMLHttpRequest 调用其 API(比如 /api/contents)。你看到的错误通常是:
Refused to display 'https://xxx:8888/tree' in a frame because an ancestor violates the following Content Security Policy directive: "frame-ancestors 'self'"
这不是 CORS 问题,而是 CSP 的 frame-ancestors 指令在起作用 —— 它和传统 CORS(Access-Control-Allow-Origin)是两套机制,必须分别处理。
c.NotebookApp.allow_origin 和 tornado_settings.headers['Content-Security-Policy'] 怎么配?
这两个配置解决的是不同层面的问题,缺一不可:
-
c.NotebookApp.allow_origin = '*':仅对部分 REST API 接口(如/api/contents)生效,让浏览器允许跨域 AJAX 请求;但它不控制 iframe 嵌入 -
c.NotebookApp.tornado_settings = {'headers': {'Content-Security-Policy': "frame-ancestors http://example.com https://myapp.net 'self'"}}:这才是决定能否被<iframe src="...">加载的关键
示例配置(写入 ~/.jupyter/jupyter_notebook_config.py):
c.NotebookApp.allow_origin = '*'
c.NotebookApp.tornado_settings = {
'headers': {
'Content-Security-Policy': "frame-ancestors http://localhost:3000 https://my-dashboard.example.com 'self'"
}
}注意:'self' 必须显式保留,否则连本地直接访问都会失败;多个来源用空格分隔,不能用逗号;HTTP 协议、端口、域名都必须精确匹配。
使用 Nginx 反向代理时,CSP 头会被覆盖吗?
会。如果你用 Nginx 做反向代理(比如把 https://notebook.example.com 代理到后端 http://127.0.0.1:8888),Nginx 默认不会透传 Jupyter 的 Content-Security-Policy,甚至可能自己加一个更严格的头。
必须在 Nginx 配置里显式设置并允许嵌入:
location / {
proxy_pass http://127.0.0.1:8888;
proxy_set_header Host $host;
# 关键:覆盖或补充 CSP,允许你的前端域名
add_header Content-Security-Policy "frame-ancestors https://myapp.example.com 'self';" always;
# 同时透传其他必要头(WebSocket 支持)
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}此时 Jupyter 自身的 tornado_settings 可以删掉或留空,全部由 Nginx 控制;但 c.NotebookApp.allow_origin 仍需设为 '*' 或具体域名,否则 API 调用会 403。
常见踩坑点:HTTPS、端口、协议不一致
哪怕只差一个字符,CSP 就会失效:
- 前端页面是
https://a.com,但配置写了http://a.com→ 拒绝嵌入 - 前端跑在
localhost:3000,但漏写了端口 →localhost≠localhost:3000 - 用了自签名证书,浏览器地址栏显示“不安全”,但 CSP 仍按
https://校验 → 必须确保 URL 协议与配置完全一致 - 启动时没加
--allow-root(root 用户下),配置文件修改无效,日志里也不报错,静默 fallback 到默认策略
最稳妥的做法:先用 curl -I http://your-jupyter-ip:8888/tree 看响应头里有没有你写的 Content-Security-Policy;再打开浏览器开发者工具 → Network → Headers → Response Headers,确认它真实生效了。


















