---

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-Typenosniff正确、Vary: Origin设置、凭证跨域仅针对白名单来源。

九、落地要点与参数校验清单

  • 回调名正则与长度限制已校验,最长128字符。
  • Access-Control-Allow-Origin必须为精确匹配来源,不能使用*与携带凭证并存。
  • Access-Control-Max-Age建议不超过600以降低策略漂移风险。
  • SSE需设置text/event-streamnosniff,并采用心跳与重试间隔控制。
  • 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 })
}

点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论
立即
投稿

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部