首先我们来了解什么是跨域
浏览器的同源策略(http协议、域名、协议三者相同)只要有一项不同,就是跨域请求,同源策略(Sameoriginpolicy)是一种约定。
协议的默认端口号
http的默认端口号是80,例如:http://www.baidu.com和http:80//www.baidu.com是同源的。
https的默认端口号是443,例如:https://www.baidu.com和http:443//www.baidu.com是同源的。
跨域,是指当前网站不能执行其他网站的脚本,他是由于浏览器的同源策略造成的,是浏览器对Javascript的安全限制。当然,如果自己做一个完全同源的网站,那么就不用考虑跨域的问题。但是,在日常开发项目中,往往跨域的请求比较多,
那么为什么会导致跨域呢?
- 服务器分离:WEB服务器、数据服务器、图片服务器…
- 云信息共享:第三方API接口
- 有助于分离开发:开发跨域、部署同源
跨域的方法
proxy跨域
proxy是我们比较常用的一种跨域方式,它是利用在本地启动一个服务器,一方面实现页面的预览,一方面实现代理发送数据请求。此时我们在发送数据请求,就要先发送给本地启动的服务,再由本地服务器发送给指定的服务器,因为服务器和服务器之间没有同源策略的限制。
本地开发的时候我们一般使用,webpack_dev_server或者nodejs来启动本地服务,而部署到服务器的时候,一般基于nainx反向代理来处理。
node.js实现
1.的依赖文件-package-json中的依赖
{
"name": "crossdomain",
"version": "1.0.0",
"description": "",
"main": "server.js",
"dependencies": {
"axios": "^0.21.1",
"blueimp-md5": "^2.18.0",
"body-parser": "^1.19.0",
"express": "^4.17.1",
"qs": "^6.9.4",
"request": "^2.88.2"
},
"devDependencies": {},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC"
}
2.件夹中创建一个server-proxy.js文件
/*-CREATE SERVER-*/
const express = require('express'),
request = require('request'),
app = express();
app.listen(1001, () => console.log(`服务启动成功,正在监听1001端口!`));
// 服务器接收到客户端发送过来的请求
app.get('/aaa', (req, res) => {
// 向简书发送相同请求,从简书服务器获取想要的数据「不存在域的限制的」
let jianURL = `https://www.jianshu.com/asimov/subscriptions/recommended_collections`;
req.pipe(request(jianURL)).pipe(res);
});
/* STATIC WEB */
app.use(express.static('./'));
3.跨域的时候在当前文件夹中打开dos命令窗口用 node server-proxy.js启动即可
4.需要发送请求的时候直接发送给代理服务器即可
axios.get('/aaa').then(response => {
console.log(response.data);
})
webpack-dev-server实现
1. 全局安装 @vue/cli
使用npm安装脚手架,$ npm i @vue/cli -g
2. 基于脚手架创建项目
$ vue create demo
3. 进入到这个目录
$ cd demo
4. 安装axios和配置config
$ npm i axios qs
5.在vue.config.js中配置webpack-dev-server
const ENV = process.env.NODE_ENV;
module.exports = {
lintOnSave: ENV !== 'production',
publicPath: './',
productionSourceMap: false,
// 对webpack-dev-server的配置
devServer: {
// 配置跨域代理「可以配置对多台服务器的代理」
proxy: {
// 所有以“/api”开始的请求,都发送到代理服务器「走这个配置」
"/api": {
target: "https://www.jianshu.com/asimov",
ws: true, //开启webscoket协议
changeOrigin: true, //改变origin源
pathRewrite: {
//将以/api替换为空 /api只是为了区分代理到那台服务器
"^/api": ""
}
},
// 所有以“/zhihu”开始的请求,都代理到知乎服务器
"/zhihu": {
target: "https://news-at.zhihu.com/api/4",
ws: true,
changeOrigin: true,
pathRewrite: {
"^/zhihu": ""
}
}
}
}
};
6.在main.js中基于axios发送请求
import { createApp } from 'vue';
import App from './App.vue';
import axios from 'axios';
// 真实发送请求的地址:http://127.0.0.1:3000/api/subscriptions/recommended_collections
// + 不加pathRewrite:向简书这样发送请求 https://www.jianshu.com/asimov/api/subscriptions/recommended_collections
// + 设置了pathRewrite:https://www.jianshu.com/asimov/subscriptions/recommended_collections
axios.get('/api/subscriptions/recommended_collections')
.then(response => {
console.log('简书:', response.data);
});
axios.get('/zhihu/news/latest')
.then(response => {
console.log('知乎:', response.data);
});
const app = createApp(App);
app.mount('#app');
7.$ npm run serve 启动本地服务
CORS跨域资源共享
CORS跨域资源共享 默认因为浏览器的安全策略 是不允许ajax跨域访问的 但是如果服务器设置了 Axxess-Control-Allow-Origin 响应头信息 设置允许这个源发送请求 那么浏览器也就不会去限制了,一般都是由后端设置。
1.由服务器设置允许源
Access-Control-Allow-Orgin 设置允许的源[白名单机制]
Access-Control-Allow-Credentials 是否允许携带资源凭证 例如cookie
服务端设置了允许源 则客户端也要配合设置允许
axios.defaults.withCredentials = true; axios设置跨域是否允许携带资源凭证
xhr.withCredentials = true 原生ajax设置跨域是否允许携带资源凭证
Access-Control-Allow-Headers 设置允许的请求头
Access-Control-Allow-Methods 设置允许的请求方式
2.在CORS跨域共享中 客户端先会发送一个OPTIONS试探请求 验证是否成功 连接后成功后 再发送真正的请求
本地使用node实验
/*-CREATE SERVER-*/
const express = require('express'),
app = express();
app.listen(1001, () => console.log(`服务启动成功,正在监听1001端口!`));
/*-MIDDLE WARE-*/
// 设置白名单
let safeList = ["http://127.0.0.1:5500", "http://127.0.0.1:3000", "http://127.0.0.1:8080"];
app.use((req, res, next) => {
let origin = req.headers.origin || req.headers.referer || "";
origin = origin.replace(/\/$/g, '');
origin = !safeList.includes(origin) ? '' : origin;
res.header("Access-Control-Allow-Origin", origin);
res.header("Access-Control-Allow-Credentials", true);
res.header("Access-Control-Allow-Headers", "Content-Type,Content-Length,Authorization, Accept,X-Requested-With");
res.header("Access-Control-Allow-Methods", "PUT,POST,GET,DELETE,OPTIONS,HEAD");
req.method === 'OPTIONS' ? res.send('OK') : next();
});
/*-API-*/
app.get('/list', (_, res) => {
res.send({
code: 0,
message: 'zhufeng'
});
});
/* STATIC WEB */
app.use(express.static('./'));
同样使用node server-cors.js 启动本地服务器
JSONP跨域
JSONP是利用了script的src不存在跨域的限制来实现的,同样< img > 、 < link > 、 < iframe >等标签也不存在跨域的限制
1.基于 < script > 的src指定请求的地址, 实现请求的发送「 没有域的限制」; 我们会创建一个“ 全局函数”, 在发送请求的时候, 会基于问号传参, 把全局函数“ 名” 基于“ callback「 自己定义: 和服务器商量好的」” 传递给服务器
const fn = function fn(data) {
console.log(data)
};
<script src = 'http://127.0.0.1:1001/list?callback=fn' >
2.服务器会收到对应的请求, 并且基于“callback” 获取传递过来的函数名 + 准备数据
服务器会收到对应的请求, 并且基于“ callback” 获取传递过来的函数名 + 准备数据[{
id: 1,
name: 'xxx'
}, {
id: 2,
name: 'xxx'
}, ...] +
把函数名和数据拼接在一起, 成为指定格式的字符串“ fn([{
id: 1,
name: 'xxx'
}, {
id: 2,
name: 'xxx'
}, ...])” +
最后返回给客户端这个字符串
3.客户端获取到“ fn(...)” 字符串, 因为基于 < script > 发送的,所以会默认把其当做js代码执行 + 也就是把全局函数fn执行,并且把服务器准备的数据当做实参传递给了函数 + 这也是为啥fn必须是全局函数了,如果是私有的, 在此处执行的时候,找不到私有的fn 问题; 注意:JSONP只能发送get请求,因为script本身只有get请求,无法发送post请求,且jsonp一定需要服务器端的配合