实现跨域请求,我们首先要先知道什么是跨域,紧接着便是跨域的方法,最后则是跨域时cookie的处理。
什么是跨域
1.什么是同源策略及其限制内容?
同源策略 是一种约定,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,浏览器很容易受到XSS 、CSRF等攻击。所谓同源是指"协议+域名+端口"三者相同,即便两个不同的域名指向同一个ip地址,也非同源。
同源策略限制内容有:
Cookie、LocalStorage、IndexedDB 等存储性内容
DOM 节点
AJAX 请求发送后,结果被浏览器拦截了
但是有三个标签是允许跨域加载资源:
<img src=XXX>
<link href=XXX>
<script src=XXX>
2.常见跨域场景
当协议 、子域名 、主域名 、端口号中任意一个不相同时,都算作不同域。不同域之间相互请求资源,就算作"跨域"。
特别说明两点:
- 第一:如果是协议和端口造成的跨域问题"前台"是无能为力的。
- 第二:在跨域问题上,仅仅是通过"URL的首部"来识别而不会根据域名对应的IP地址是否相同来判断。"URL的首部"可以理解为"协议, 域名和端口必须匹配"。
跨域并不是请求发不出去,请求能发出去,服务端能收到请求并正常返回结果,只是结果被浏览器拦截了。你可能会疑问明明通过表单的方式可以发起跨域请求,为什么 Ajax 就不会?因为归根结底,跨域是为了阻止用户读取到另一个域名下的内容,Ajax 可以获取响应,浏览器认为这不安全,所以拦截了响应。但是表单并不会获取新的内容,所以可以发起跨域请求。同时也说明了跨域并不能完全阻止 CSRF,因为请求毕竟是发出去了。
跨域有哪些方案?
1.CORS
CORS 通信过程都是浏览器自动完成,需要浏览器(都支持)和服务器都支持,所以关键在只要服务器支持,就可以跨域通信 ,CORS请求分两类,简单请求 和非简单请求
另外CORS请求默认不包含Cookie以及HTTP认证信息,如果需要包含Cookie,需要满足几个条件:
- 服务器指定了 Access-Control-Allow-Credentials: true
- 开发者须在请求中打开withCredentials 属性: xhr.withCredentials = true
- Access-Control-Allow-Origin 不要设为星号,指定明确的与请求网页一致的域名,这样就不会把其他域名的Cookie上传
简单请求
需要同时满足两个条件,就属于简单请求:
- 请求方法是:HEAD、GET、POST,三者之一
- 请求头信息不超过以下几个字段:
- Accept
- Accept-Language
- Content-Language
- Last-Event-Id
- Content-Type:值为三者之一application/x-www/form/urlencoded 、multipart/form-data 、text/plain
需要这些条件是为了兼容表单,因为历史上表单一直可以跨域
浏览器直接发出CORS请求,具体来说就是在头信息中增加Origin字段,表示请求来源来自哪个域(协议+域名+端口),服务器根据这个值决定是否同意请求。如果同意,返回的响应会多出以下响应头信息
javascript
const xhr = new XMLHttpRequest();
xhr.withCredentials = true;
xhr.onreadystatechange = function() {
if (xhr.readyState === 4 && xhr.status === 200) {
console.log('收到数据:', xhr.responseText);
}
};
xhr.open('GET', 'http://example.com/data.json', true);
xhr.send();
javascript
Access-Control-Allow-Origin: http://yourdomain.com
Access-Control-Allow-Credentials: true
在这个示例中,我们使用了 XMLHttpRequest 对象来进行跨域请求。我们设置了 withCredentials 属性为 true,表示允许跨域请求。同时,在服务器响应中需要设置以上响应头,其中 Access-Control-Allow-Origin 表示允许哪个域名下的请求,Access-Control-Allow-Credentials 表示是否允许发送凭证(如 cookie、HTTP 认证等)。
非简单请求
比如 PUT 或 DELETE 请求,或 Content-Type 为 application/json ,就是非简单请求。
非简单 CORS 请求,正式请求前会发一次 OPTIONS 类型的查询请求,称为预检请求,询问服务器是否支持网页所在域名的请求,以及可以使用哪些头信息字段。只有收到肯定的答复,才会发起正式XMLHttpRequest请求,否则报错
预检请求的方法是OPTIONS,它的头信息中有几个字段
- Origin: 表示请求来自哪个域,这个字段是必须的
- Access-Control-Request-Method:列出CORS请求会用到哪些HTTP方法,这个字段是必须的
- Access-Control-Request-Headers: 指定CORS请求会额外发送的头信息字段,用逗号隔开
OPTIONS请求次数过多也会损耗性能,所以要尽量减少OPTIONS请求,可以让服务器在请求返回头部添加
javascript
Access-Control-Max-Age: Number // 数字 单位是秒
2.JSONP
JSONP 是一种常见的跨域请求方法。它是通过在 HTML 页面中添加一个 script 标签,来请求一个位于其他域名下的 JavaScript 文件。该 JavaScript 文件返回的数据需要使用一个回调函数进行包装,以便在原始页面中可以接收到数据。
javascript
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>JSONP 示例</title>
</head>
<body>
<script>
function jsonpCallback(data) {
console.log('收到数据:', data);
}
const url = 'http://example.com/data.json?callback=jsonpCallback';
const script = document.createElement('script');
script.src = url;
document.body.appendChild(script);
</script>
</body>
</html>
在这个示例中,我们通过添加一个 script 标签来请求 http://example.com/data.json 这个地址下的数据。由于该地址存在跨域问题,我们指定了一个名为 jsonpCallback 的回调函数,并将回调函数名作为参数传递到了地址中。当服务器返回数据时,会自动调用回调函数并将数据作为参数传入。
3.代理
如果无法使用 JSONP 或 CORS,可以尝试使用代理服务器来转发请求。在前端中,可以向自己的服务器发送请求,然后让服务器代理请求其它域名下的资源,并将结果返回给前端。
javascript
const xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
if (xhr.readyState === 4 && xhr.status === 200) {
console.log('收到数据:', xhr.responseText);
}
};
xhr.open('GET', '/proxy?url=http://example.com/data.json', true);
xhr.send();
在这个示例中,我们向自己的服务器发送了一个 GET 请求,并在请求地址中添加了一个名为 proxy 的参数。服务器接收到请求后会将该请求转发到 http://example.com/data.json 地址下,并将响应结果返回给前端。
4.WebSocket
WebSocket 是一种支持跨域请求的协议,它允许客户端和服务器之间建立一个双向的通信通道。通过 WebSocket 可以实现跨域传输数据。
因为WebSocket请求头信息中有Origin 字段,表示请求源来自哪个域,服务器可以根据这个字段判断是否允许本次通信,如果在白名单内,就可以通信
客户端代码:
javascript
const socket = new WebSocket('ws://example.com/ws');
socket.onopen = function() {
console.log('连接已建立');
socket.send('你好,服务器!');
};
socket.onmessage = function(event) {
console.log('收到消息:', event.data);
};
socket.onerror = function(event) {
console.log('发生错误:', event);
};
在这个示例中,我们通过创建一个 WebSocket 对象来连接 ws://example.com/ws 这个地址下的 WebSocket 服务。当连接建立成功后,我们发送了一段消息给服务器。当服务器返回消息时,会自动触发 onmessage 事件,并将服务器发送的数据作为参数传入。
需要注意的是,在使用 WebSocket 进行跨域通信时,服务器必须显式地指定允许哪些源访问 WebSocket 服务。服务器需要在握手阶段返回一个 Upgrade 头部,并在该头部中指定 Origin 头部的值。以下是一个使用 Node.js 实现的 WebSocket 服务的示例代码:
javascript
const WebSocketServer = require('ws').Server;
const wss = new WebSocketServer({ port: 8080 });
wss.on('connection', function(ws) {
console.log('连接已建立');
ws.on('message', function(message) {
console.log('收到消息:', message);
ws.send('你好,客户端!');
});
});
在这个示例中,我们使用了 ws 模块创建了一个 WebSocket 服务,并监听在 8080 端口上。当客户端连接成功后,会触发 connection 事件,并返回一个 WebSocket 对象 ws。当客户端发送消息时,会自动触发 message 事件,并将客户端发送的数据作为参数传入。在这个示例中,我们将收到的消息直接返回给客户端。
5.postMessage
postMessage 是一种在不同窗口或跨域框架之间进行通信的机制。它允许一个窗口向另一个窗口发送消息,无论这两个窗口是否同源。
使用 postMessage 进行通信时,需要提供目标窗口的引用,并通过调用 postMessage 方法发送消息。以下是一个示例:
javascript
// 发送消息
const targetWindow = window.opener; // 获取目标窗口的引用
const message = 'Hello, world!';
const targetOrigin = 'http://example.com'; // 目标窗口的源
targetWindow.postMessage(message, targetOrigin);
// 接收消息
window.addEventListener('message', function(event) {
if (event.origin === 'http://example.com') {
const message = event.data;
console.log('收到消息:', message);
// 处理接收到的消息
}
});
在这个示例中,首先获取了目标窗口的引用 targetWindow ,然后通过调用 postMessage 方法向目标窗口发送消息。发送的消息可以是任意类型的数据,例如字符串、对象等。
在接收消息的窗口中,通过添加 message 事件监听器来监听消息的接收,然后通过 event.data 获取收到的消息内容。可以通过 event.origin 来验证消息来源的域,确保只接受来自指定域的消息。
需要注意的是,为了确保安全性,应该始终在 postMessage 中指定目标窗口的源,以防止恶意窗口的干扰。同时,在接收消息时,应该对消息来源进行验证,并仅处理来自可信任源的消息。
总结来说,postMessage 提供了一种安全的跨窗口通信机制,可以在不同窗口、跨域框架之间进行双向通信。
这里只介绍几种开发中用的比较多的,几乎用不到的比如:
document.domain + iframe
:适用主域名相同,子域名不同的跨域场景window.name + iframe
:利用name值最长可以 2M ,并用不同页面或不同域名加载后依然存在的特性location.hash + iframe
:适用通过 C 页面来实现 A 页面与 B 页面通信的场景
跨域时 Cookie 要做何处理?
在跨域请求中,Cookie 的处理需要额外注意。默认情况下,跨域请求是不会携带 Cookie 的,这是出于安全考虑。
如果你想在跨域请求中携带 Cookie,有以下几种方法:
-
CORS(跨域资源共享)
:在服务端设置响应头部,允许指定的源携带 Cookie 。具体做法是,在服务器返回的响应头中添加 Access-Control-Allow-Credentials: true ,表示允许携带凭证(如 Cookie);同时,需要指定允许访问的源,可以使用 Access-Control-Allow-Origin: http://yourdomain.com 来限制只允许特定域名的请求携带 Cookie。 -
代理
:将跨域请求发送到自己的服务器上,然后再由自己的服务器转发请求,并将响应返回给前端。由于请求是在同源下发起的,因此可以携带 Cookie。前端将请求发送给自己的服务器,然后自己的服务器再将请求发送给目标服务器,并将目标服务器的响应返回给前端。 -
在 URL 中传递参数
:可以将需要的信息以参数的形式附加在 URL 上,而不是通过 Cookie 传递。这样不会触发同源策略,也不会引起跨域问题。
需要注意的是,使用 Cookie 进行跨域请求时,还需要设置 XMLHttpRequest 对象的 withCredentials 属性为 true。例如:
javascript
const xhr = new XMLHttpRequest();
xhr.withCredentials = true;
xhr.open('GET', 'http://example.com/api', true);
xhr.send();
同时,在服务端也要对跨域请求进行相应的处理,以确保安全性。