跨域是指浏览器允许向服务器发送跨域请求,从而克服Ajax只能同源使用的限制。所以解决跨域问题还是无法避免的。
首先了解一下浏览器的同源策略/SOP(Same origin policy),它是浏览器最核心也最基本的安全功能。
所谓同源是指 "协议+域名+端口"三者相同,即便两个不同的域名指向同一个ip地址,也非同源。限制了不同源之间资源的交互。
并且,
跨域并不是请求发不出去,而是请求发出去了,也正常返回结果了,但是结果被浏览器拦截了
。所显示的跨域交互包括:
- 无法读取非同源网页的 Cookie、LocalStorage 和 IndexedDB
- 无法接触非同源网页的 DOM
- 无法向非同源地址发送 AJAX 请求
1. 通过jsonp跨域
jsonp的原理就是利用<script>
标签没有跨域限制,通过<script>
标签的src属性,发送带有callback参数的GET请求,服务端将接口返回数据拼凑到callback函数中,返回给浏览器,浏览器解析执行,从而前端拿到callback函数返回的数据。
jsonp缺点:只能发送GET一种请求
js
function jsonp(url, params, callback) {
// 生成唯一的回调函数名
const callbackName = 'jsonp_' + Date.now();
// 将参数拼接到 URL 中
const queryString = Object.keys(params)
.map(key => encodeURIComponent(key) + '=' + encodeURIComponent(params[key]))
.join('&');
// 创建 script 元素
const script = document.createElement('script');
script.src = url + '?' + queryString + '&callback=' + callbackName;
// 定义回调函数,服务器返回数据后被调用
window[callbackName] = function(data) {
// 调用回调函数
callback(data);
// 删除 script 元素和回调函数
document.head.removeChild(script);
delete window[callbackName];
};
// 将 script 元素添加到页面中
document.head.appendChild(script);
}
使用示例:
js
jsonp('http://www.example.com/api', { user: 'admin' }, function(data) {
console.log(data);
});
这个 jsonp
函数接受三个参数:URL、参数对象和回调函数。它会生成一个唯一的回调函数名,并将参数拼接到 URL 中。然后创建一个 <script>
元素,并将 URL 设置为带有回调函数名的 URL。定义一个全局的回调函数,当响应返回时调用该回调函数,并将数据传递给回调函数。最后将 <script>
元素添加到页面中,触发跨域请求。当请求完成后,删除 <script>
元素和回调函数。
2. document.domain + iframe跨域
此方案仅限主域相同,子域不同的跨域应用场景
这两个域名必须属于同一个一级域名!而且所用的协议,端口都要一致,否则无法利用document.domain进行跨域。主域名又称一级域名或者顶级域名,由域名主体.域名后缀组成,比如domain.com
子域名有二级域名,比如www.domain.com。 三级域名,比如home.m.domain.com。
实现原理:两个页面都通过js强制设置document.domain为基础主域,就实现了同域。
1) 父窗口(www.domain.com/a.html)
html
<iframe id="iframe" src="http://child.domain.com/b.html"></iframe>
<script>
document.domain = 'domain.com';
var user = 'admin';
</script>
js
document.domain = 'domain.com';
// 获取父窗口中的变量
alert('get js data from parent ---> ' + window.parent.user);
3. nginx代理跨域
通过 Nginx 配置反向代理,将跨域请求转发到同源接口,从而避免浏览器的同源策略限制。
ini
# proxy服务器
server {
listen 80;
server_name your-domain.com;
location /api {
# 设置代理目标地址
proxy_pass http://api.example.com;
# 设置允许的跨域请求头
add_header Access-Control-Allow-Origin $http_origin;
add_header Access-Control-Allow-Methods "GET, POST, OPTIONS";
add_header Access-Control-Allow-Credentials true;
add_header Access-Control-Allow-Headers "Origin, X-Requested-With, Content-Type, Accept";
# 处理预检请求(OPTIONS 请求)
if ($request_method = OPTIONS) {
return 200;
}
}
}
在上面的示例中,假设你的域名是 your-domain.com
,需要代理访问 api.example.com
。你可以将这个配置添加到 Nginx 的配置文件中。
这个配置会将 /api
路径下的请求代理到 http://api.example.com
。同时,通过添加 Access-Control-Allow-*
头部,允许跨域请求的来源、方法、头部等。
这样,当你在前端发送请求到 /api
路径时,Nginx 会将请求代理到 http://api.example.com
,并在响应中添加跨域相关的头部,从而解决跨域问题。注意要根据实际情况进行配置,包括监听的端口、域名和代理的目标地址等。
4. nodejs中间件代理跨域
使用 Node.js 构建一个中间件,在服务器端代理请求,将跨域请求转发到同源接口,然后将响应返回给前端。
可以使用 http-proxy-middleware
模块来创建一个简单的代理服务器
js
const express = require('express');
const { createProxyMiddleware } = require('http-proxy-middleware');
const app = express();
// 创建代理中间件
const apiProxy = createProxyMiddleware('/api', {
target: 'http://api.example.com', // 设置代理目标地址
changeOrigin: true, // 修改请求头中的 Origin 为目标地址
pathRewrite: {
'^/api': '', // 重写请求路径,去掉 '/api' 前缀
},
// 可选的其他配置项...
});
// 将代理中间件应用到 '/api' 路径
app.use('/api', apiProxy);
// 启动服务器
app.listen(3000, () => {
console.log('Proxy server is running on port 3000');
});
在上面的示例中,首先使用 express
框架创建一个服务器实例。然后,使用 http-proxy-middleware
模块创建一个代理中间件。通过配置代理中间件的 target
选项,将请求代理到目标地址 http://api.example.com
。
你可以通过其他可选的配置项来进行更多的定制,例如修改请求头、重写请求路径等。在这个示例中,我们将代理中间件应用到路径 /api
下,即当请求路径以 /api
开头时,会被代理到目标地址。
最后,启动服务器并监听指定的端口(这里是 3000)。
请确保你已经安装了 express
和 http-proxy-middleware
模块,并将上述代码保存为一个文件(例如 proxy-server.js
)。然后通过运行 node proxy-server.js
来启动代理服务器。
现在,当你在前端发送请求到 /api
路径时,Node.js 代理服务器会将请求转发到 http://api.example.com
,从而实现跨域访问。记得根据实际情况修改目标地址和端口号。
5. 通过webpack devserver代理
使用 webpack-dev-server
的代理功能可以实现在开发过程中的跨域请求。配置 devServer
对象中的 proxy
选项来设置代理。
js
module.exports = {
// 其他配置项...
devServer: {
proxy: {
'/api': {
target: 'http://api.example.com', // 设置代理目标地址
pathRewrite: { '^/api': '' }, // 重写请求路径,去掉 '/api' 前缀
changeOrigin: true, // 修改请求头中的 Origin 为目标地址
},
},
},
};
在上面的示例中,我们配置了一个代理,将以 /api
开头的请求转发到 http://api.example.com
。通过 pathRewrite
选项,我们去掉了请求路径中的 /api
前缀,以符合目标地址的接口路径。
将上述配置添加到你的 webpack.config.js
文件中,然后启动 webpack-dev-server
。现在,当在前端发送以 /api
开头的请求时,webpack-dev-server
会将请求转发到目标地址,并返回响应结果。
注意,这里的配置是针对开发环境下的代理,当构建生产环境的代码时,代理配置不会生效。
请确保已经安装了 webpack-dev-server
,并在 package.json
文件的 scripts
中添加启动命令,例如:
json
{
"scripts": {
"start": "webpack-dev-server --open"
}
}
运行 npm start
或 yarn start
来启动 webpack-dev-server
。
这样,通过配置 webpack-dev-server
的代理,就可以在开发过程中实现跨域请求。记得根据实际情况修改目标地址和请求路径。
6. CORS(跨域资源共享)
在服务端设置响应头部,允许特定的域名或所有域名访问该资源。可以通过在响应头部中设置 Access-Control-Allow-Origin
字段来指定允许访问的域名。
CORS请求设置的响应头字段,都以 Access-Control-开头:
1)Access-Control-Allow-Origin :必选
它的值要么是请求时Origin字段的值,要么是一个*,表示接受任意域名的请求。
2)Access-Control-Allow-Credentials :可选
它的值是一个布尔值,表示是否允许发送Cookie。默认情况下,Cookie不包括在CORS请求之中。设为true,即表示服务器明确许可,Cookie可以包含在请求中,一起发给服务器。这个值也只能设为true,如果服务器不要浏览器发送Cookie,删除该字段即可。
3)Access-Control-Expose-Headers :可选
CORS请求时,XMLHttpRequest对象的getResponseHeader()方法只能拿到6个基本字段:Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma。如果想拿到其他字段,就必须在Access-Control-Expose-Headers里面指定。上面的例子指定,getResponseHeader('FooBar')可以返回FooBar字段的值。
js
const express = require('express');
const app = express();
// 允许所有域名访问
app.use((req, res, next) => {
res.setHeader('Access-Control-Allow-Origin', '*');
next();
});
// 路由和处理逻辑
// ...
app.listen(3000, () => {
console.log('Server is running on port 3000');
});
7. WebSocket
使用 WebSocket 协议进行通信,WebSocket 不受同源策略限制,因此可以在不同域之间进行双向通信。
利用webSocket的API,可以直接new一个socket实例,然后通过open方法内send要传输到后台的值,也可以利用message方法接收后台传来的数据。后台是通过new WebSocket.Server({port:3000})实例,利用message接收数据,利用send向客户端发送数据。
js
const socket = new WebSocket('ws://example.com/socket');
socket.onopen = () => {
console.log('WebSocket connection established.');
// 发送数据
socket.send('Hello, server!');
};
socket.onmessage = (event) => {
console.log('Received message from server:', event.data);
};
socket.onclose = () => {
console.log('WebSocket connection closed.');
};
原生WebSocket API使用起来不太方便,我们可以使用Socket.io,它很好地封装了webSocket接口,提供了更简单、灵活的接口,也对不支持webSocket的浏览器提供了向下兼容。