Node系列 · Express:跨域之 JSONP
JSONP 是 CORS 之前的"老式"跨域方案——利用
<script>标签不受同源策略限制的特性。本章讲清楚 JSONP 的工作原理、Express 实现、以及它为什么被 CORS 取代。
一、同源策略与跨域
1.1 同源
text
页面源:http://a.com/page.html
接口源:http://b.com/api/users判定标准:协议 + 域名 + 端口 都相同才算同源。
| 例子 | 是否同源 |
|---|---|
http://a.com/x → http://a.com/y | ✅ |
http://a.com/x → https://a.com/x | ❌(协议不同) |
http://a.com/x → http://b.com/x | ❌(域名不同) |
http://a.com/x → http://a.com:8080/x | ❌(端口不同) |
1.2 浏览器不允许非同源请求
浏览器主动拦截 XMLHttpRequest / fetch 跨域响应——但 <img> / <script> / <link> 等标签不受同源限制。
JSONP 利用的就是 <script> 的"特权"。
二、JSONP 原理
- 客户端定义全局函数
window.fn - 动态创建
<script src="http://b.com/api?callback=fn"> - 服务端把数据包在 fn 调用里返回:
fn([...]) - 浏览器把响应当 JS 执行,调用
fn函数 - 客户端拿到数据
2.1 客户端代码
html
<script>
function handleUsers(users) {
console.log('拿到数据:', users);
}
</script>
<!-- 动态加载 script -->
<script src="http://b.com/api/users?callback=handleUsers"></script>2.2 服务端代码(Express)
javascript
app.get('/api/users', (req, res) => {
const users = [{ id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }];
const callback = req.query.callback;
if (callback) {
// JSONP 模式:把数据包在函数调用里
res.type('application/javascript');
res.send(`${callback}(${JSON.stringify(users)})`);
} else {
// 普通 JSON 模式
res.json(users);
}
});响应内容:
javascript
handleUsers([{"id":1,"name":"Alice"},{"id":2,"name":"Bob"}])浏览器执行这段 JS → 调用 handleUsers → 客户端拿到数据。
三、JSONP 的致命缺陷
3.1 只能 GET
<script> 标签天然只支持 GET 请求——POST / PUT / DELETE 没法做。
3.2 难调试
服务端返回的是 JS 代码而非 JSON——浏览器 Network 面板看不到结构化响应,只能看到一段 JS 字符串。
3.3 安全风险
javascript
// 服务端可以执行客户端传来的 callback 函数名
const callback = req.query.callback;
res.send(`${callback}(${data})`);
// 如果 callback = "alert(1);evil()"
// 服务端返回:alert(1);evil({...})
// 浏览器执行:alert(1) + 调用 evil 函数DANGER
JSONP 存在反射型 XSS 风险——callback 参数如果不过滤就被攻击者利用:
javascript
// ✅ 正确:限制 callback 只能是合法标识符
const callback = req.query.callback;
if (!/^[a-zA-Z_$][\w$]*$/.test(callback)) {
return res.status(400).json({ error: 'invalid callback' });
}
res.send(`${callback}(${JSON.stringify(data)})`);必须严格校验 callback——只允许字母数字下划线。
3.4 错误处理难
JSONP 失败只能靠超时——没有 HTTP 状态码,没有错误信息。
四、被 CORS 取代
CORS(Cross-Origin Resource Sharing)是 W3C 推荐的现代方案:
| 维度 | JSONP | CORS |
|---|---|---|
| HTTP 方法 | 仅 GET | 任意 |
| 错误处理 | HTTP 状态码 | 标准 |
| 安全 | callback 注入风险 | 受控 Origin 白名单 |
| 调试 | 难 | 标准 HTTP 响应 |
| 浏览器支持 | 所有浏览器 | 所有现代浏览器 |
TIP
新项目不要用 JSONP。CORS 是更现代、更安全的方案,详见 cors 中间件 章节。
五、历史场景
JSONP 只在以下场景还有价值:
- 调用 IE8/IE9 的老接口(不支持 CORS)
- 调用只暴露 JSONP 的第三方接口(无法让对方改)
否则一律用 CORS。
六、最佳实践
| 场景 | 推荐 |
|---|---|
| 新项目跨域 | CORS(详见 跨域 CORS 与 cors 中间件) |
| 维护老 JSONP 接口 | 必须严格过滤 callback 参数 |
| 客户端调用 | 优先用 fetch / axios + CORS,不要再造 JSONP 轮子 |
| 服务端 JSONP | callback 强校验(只允许合法标识符) |
七、小结
- JSONP 利用
<script>标签不受同源策略限制的特性 - 客户端定义 callback 函数 → 服务端把数据包在 callback 调用里返回
- 致命缺陷:仅 GET / 难调试 / callback 注入风险 / 错误处理难
- 新项目用 CORS,不要用 JSONP
- 维护 JSONP 接口时严格过滤 callback(只允许合法标识符),防 XSS
