Skip to content
On this page

跨域问题

问题背景

因为浏览器出于安全考虑,有同源策略。如果协议、域名 或者端口有一个不同就是跨域,Ajax 请求会失败。

主要是用来防止CSRF攻击的,避免攻击者利用用户的登录态通过脚本等方式去获取非同源的恶意数据。

一个典型的CSRF攻击有着如下的流程:

在没有同源策略的情况下,网站可以被任意来源的Ajax访问到内容,如果此时还存在登录态,那么对方可以通过Ajax获取你的任何信息。

那么,请求到底发出去了没有?发出去了,只是浏览器觉得不安全,所以拦截了响应。

跨域解决方案

JSONP

利用<script>标签没有跨域限制的漏洞。

通过<script>指向一个需要访问的地址并提供一个回调函数来接收数据。

  • 使用简单且兼容性不错
  • 只限于get请求
js
function jsonp(url, jsonpCallback, success) { 
	let script = document.createElement('script');
	script.src = url;
	script.async = true;
	script.type = 'text/javascript';
	window[jsonpCallback] = function(data) {
		success && success(data);
	}
	document.body.appendChild(script); 
}

jsonp('http://xxx', 'callback', function(value) {
	console.log(value);
})

// 后端返回 包裹数据的函数调用
callback({...具体的数据})

CORS

  • 需要浏览器和后端同时支持,关键在后端
  • IE8/9需要通过XDomainRequest来实现

服务端设置响应头Access-Control-Allow-Origin就可以开启CORS,该属性表示哪些域名可以访问资源,如果设置通配符则表示所以的网站都可以访问资源。

这套机制建立在一个核心内容基础上:http headers,定义了一系列的HTTP头,通过这些HTTP头来控制资源的访问。 跨源资源共享(CORS) - HTTP | MDN

通过这种方式解决跨域问题,发送请求时会有两种情况:

  • 简单请求(包括以下条件)
    • 使用一下方法:GET/HEAD/POST
    • Content-Type为以下三者之一:text/plainmultipart/form-dataapplication/x-www-form-urlencoded
  • 复杂请求
    • 先发送method为options的请求,称为预检请求,后端需要对这个预检请求进行处理,返回对应的头信息,告知客户端是否允许发送非简单请求。

对于预检请求来说,如果你使用过 Node 来设置 CORS 的话,可能会遇到过这么一个坑。

(见下方代码)

该请求会验证你的 Authorization 字段,没有的话就会报错。

此时预检请求也会进入回调中,也会触发 next 方法,因为预检请求并不包含 Authorization 字段,服务端就会报错。

所以,需要在回调中过滤 option 方法。

js
 app.use((req, res, next) => { 
	res.header('Access-Control-Allow-Origin', '*');
	res.header('Access-Control-Allow-Methods','PUT, GET, POST, DELETE, OPTIONS');
	res.header('Access-Control-Allow-Headers',
	'Origin, X-Requested-With, Content-Type, Accept, Authorization, Access-Control-Allow-Credentials');
	next(); 
})

document.domain

适用于二级域名相同的情况下,比如a.test.comb.test.com

只需要给页面添加document.domain = 'test.com'即可表示二级域名都相同就可以实现跨域

postMessage

这种方式通常用于获取嵌入⻚面中的第三方⻚面数据。一个⻚面发送消息,另一个⻚面判断来源并接收消息。包含有以下几种场景:

  • 页面和其打开的新窗口的数据传递
  • 多窗口之间的消息传递
  • 页面与嵌套iframe消息传递
  • 上面三种场景的跨域数据传递
js
// 发送消息端 
window.parent.postMessage('message', 'http://test.com')
// 接收消息端
var mc = new MessageChannel();
mc.addEventListener('message', event => {
	var origin = event.origin || event.originalEvent.origin;
	// 如果是目标地址,通过,执行相关操作
	if (origin === 'http://test.com') {
		console.log('验证通过');
	}
});

跨域解决方案:后端代理

除了js能通过一些API发送HTTP请求,其他语言也可以实现,同源策略仅仅是浏览器下的某种策略机制,其他语言不受限制,所以我们可以利用静态资源(也就是js发送请求所在的资源服务器)去发送请求,然后进行转发,我们称为“代理”。

  • 正向代理
    • {客户端脚本 (xhr)} ⇒ {API服务器} :跨域
    • {客户端脚本(xhr)⇒ 代理 } ⇒ {API服务器}:跨域
  • 反向代理
    • {客户端(xhr)} ⇒ { 代理 ⇒ API服务器 } :跨域
  • 使用第三方中间件
    • koa-server-http-proxy(正向代理)
javascript
const Koa = require('koa')
const app = new Koa()
const proxy = require('koa-server-http-proxy')
app.use(proxy('/api', {
	target: 'https://news-at.zhihu.com',
	//把当前请求的path转成目标服务器实际path
    pathRewrite: { '^/api': '' },
	changeOrigin: true
})) 
app.listen(3000)

跨域凭证信息处理

  • 基于Cookie的CORS处理
    • cookie实际上也会受到同源策略的限制,如果是非同源请求,cookie是默认被禁止携带的。
    • 处理方式
      • 客户端在请求中设置:withCredentials : true
      • 服务端要在cros中设置:ctx.set('Assess-Control-Allow-Credentials' , 'true')
  • 基于Token的鉴权机制
    • 基于Cookie的验证会存在一些问题:
      • 无论当前请求是否需要传输Cookie,浏览器都会主动发送(浪费)
      • 容易被CSRF攻击
    • 使用JWT
      • Json web token,为了在网络应用环境间传递声明而执行的一种基于JSON的开发标准。JWT.IO - JSON Web Tokens Introduction
      • 组成结构:由通过 . 链接的三段文本信息构成
javascript
let header = {
	"alg": "HS256",
	"typ": "JWT"
}
let payload = {
	"sub": "1234567890",
	"name": "John Doe",
	"admin": true
}
let sign = HMACSHA256(
	base64UrlEncode(header) + "." +
	base64UrlEncode(payload),
	secret)
            
let token = `${base64UrlEncode(header)}.${base64UrlEncode(payload)}.${sign}`;
  • 使用jsonwebtoken
javascript
//生成token
const jwt = require('jsonwebtoken');
let key = 'merlin'
let token = jwt.sign({ uid: 1 }, key);

ctx.set('Aythorization',token)

//验证token
try {
   let uid = jwt.verify(token, key);
} catch(err) {
   // err
}

MIT Licensed | Copyright © 2021 - 2022