---
title: JSONP与旧式跨域传输风险识别与替换最佳实践
keywords:
- JSONP
- 跨域
- CORS
- XSS
- CSRF
- SameSite
- postMessage
- SSE
- WebSocket
- 安全替代
- Callback注入
- MIME类型
- nosniff
description: 系统识别JSONP与旧式跨域传输的核心风险(回调注入、执行上下文XSS、MIME嗅探、缓存污染、CSRF等),并提供可验证的安全替代方案(CORS+SSE/WebSocket/postMessage),含服务器与前端的落地示例与参数校验。
categories:
- 文章资讯
- 编程技术
---
一、背景与核心风险
- JSONP以
<script>方式加载并在调用者上下文中直接执行,存在回调名注入与任意代码执行风险。 - 缺乏
Content-Type约束与nosniff防护,易受MIME嗅探与XSSI影响。 - 依赖Cookie跨域时可能触发CSRF与会话泄露,且无法做细粒度权限与方法控制。
- 缓存与CDN场景中易被投毒,缺少
Vary与来源校验导致污染传播。
二、风险识别与拦截策略
- 拒绝
callback参数并统一返回JSON;对历史接口进行网关级阻断与灰度迁移。 - 若临时兼容,必须严格校验回调名并对返回值使用JSON前缀防XSSI,同时禁止携带敏感数据。
- 统一开启
X-Content-Type-Options: nosniff与精确Content-Type,并设置最小CSP限制。
三、禁用JSONP与统一响应
type Res = { setHeader: (k: string, v: string) => void; status: (n: number) => Res; end: (b?: string) => void }
type Req = { query: Record<string, string | undefined> }
function sendJson(res: Res, data: any) {
res.setHeader('Content-Type', 'application/json; charset=utf-8')
res.setHeader('X-Content-Type-Options', 'nosniff')
res.end(JSON.stringify(data))
}
function rejectJsonp(req: Req, res: Res) {
const hasCallback = typeof req.query['callback'] === 'string'
if (hasCallback) return res.status(400).end('jsonp_not_supported')
}
四、临时兼容场景的严格校验(仅迁移期)
function isSafeJsonpCallback(name: string): boolean {
if (name.length > 128) return false
const re = /^[A-Za-z_$][A-Za-z0-9_$]*(\.[A-Za-z_$][A-Za-z0-9_$]*)*$/
return re.test(name)
}
function jsonWithPrefix(obj: any): string {
const prefix = ")]}',\n"
return prefix + JSON.stringify(obj)
}
function sendJsonp(res: Res, cb: string, data: any) {
if (!isSafeJsonpCallback(cb)) return res.status(400).end('invalid_callback')
res.setHeader('Content-Type', 'application/javascript; charset=utf-8')
res.setHeader('X-Content-Type-Options', 'nosniff')
res.end(`${cb}(${jsonWithPrefix(data)})`)
}
五、CORS安全替代与精确配置
type OriginCheck = (o: string) => boolean
function corsHeaders(origin: string, check: OriginCheck) {
const headers: Record<string, string> = {}
if (check(origin)) {
headers['Access-Control-Allow-Origin'] = origin
headers['Vary'] = 'Origin'
headers['Access-Control-Allow-Credentials'] = 'true'
headers['Access-Control-Allow-Methods'] = 'GET,POST'
headers['Access-Control-Allow-Headers'] = 'Content-Type,Authorization'
headers['Access-Control-Max-Age'] = '600'
}
return headers
}
function applyCors(req: { headers: Record<string, string | undefined>; method: string }, res: Res, allow: Set<string>) {
const origin = req.headers['origin'] || ''
const hs = corsHeaders(origin, o => allow.has(o))
for (const [k, v] of Object.entries(hs)) res.setHeader(k, v)
if (req.method === 'OPTIONS') return res.status(204).end()
}
六、SSE与WebSocket作为推送替代
import { ServerResponse } from 'http'
function sse(res: ServerResponse) {
res.setHeader('Content-Type', 'text/event-stream; charset=utf-8')
res.setHeader('Cache-Control', 'no-cache')
res.setHeader('X-Content-Type-Options', 'nosniff')
res.write('retry: 5000\n')
res.write(`data: ${JSON.stringify({ ok: true })}\n\n`)
}
type WsReq = { origin: string; token: string }
function wsAllowedOrigin(req: WsReq, allow: Set<string>): boolean {
return allow.has(req.origin)
}
function wsAuthorize(token: string): boolean {
return /^[A-Za-z0-9_\-\.]{16,}$/.test(token)
}
七、postMessage跨域整合
type Msg = { type: string; payload?: any }
function safePostMessage(target: Window, origin: string, message: Msg) {
target.postMessage(message, origin)
}
function onMessage(ev: MessageEvent) {
const allow = new Set(['https://example.com'])
if (!allow.has(ev.origin)) return
const msg = ev.data as Msg
if (typeof msg?.type !== 'string') return
}
八、迁移与验收步骤
- 扫描并统计带
callback参数的历史端点与调用方,建立清单与分级风险。 - 网关拦截并返回
jsonp_not_supported,前端改为fetch+CORS或SSE/WebSocket。 - 验收项:无
callback参数、Content-Type与nosniff正确、Vary: Origin设置、凭证跨域仅针对白名单来源。
九、落地要点与参数校验清单
- 回调名正则与长度限制已校验,最长128字符。
Access-Control-Allow-Origin必须为精确匹配来源,不能使用*与携带凭证并存。Access-Control-Max-Age建议不超过600以降低策略漂移风险。- SSE需设置
text/event-stream与nosniff,并采用心跳与重试间隔控制。 - WebSocket需检查
Origin与令牌格式,握手失败立即关闭。
十、示例:统一接口处理流程
function handle(req: Req & { headers: Record<string, string | undefined>; method: string }, res: Res) {
rejectJsonp(req, res)
applyCors({ headers: req.headers, method: req.method }, res, new Set(['https://example.com']))
sendJson(res, { ok: true })
}

发表评论 取消回复