# 介绍
# 同源策略
- 协议 // 域名 : 端口号
- 完全一致才叫同源,否则会产生跨域
- 这个同源策略是W3C对浏览器的异步请求的限制,也就是说服务器和html内嵌的资源请求没有此限制
- 跨域资源是可以发送的,但是接收的数据时候会被浏览器限制
- 本端口号【8888】 -> 取异步【8889】的数据,报错
- 但是image拿百度的图片资源,能拿到
const express = require('express');
const app = new express();
app.listen(8888,() => {
console.log('listen 8888');
});
app.get('/', (req, res) => res.send(`
<body>
<div>首页</div>
<image src='https:////www.baidu.com/img/flexible/logo/pc/result@2.png'/>
<script>
setTimeout(() => {
fetch('http://localhost:8889')
.then(res => res.text())
.then(res => console.log(res)); // error
}, 2000);
</script>
</body>
`));
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const express = require('express');
const app = new express();
app.listen(8889, () => {
console.log('listen 8889');
});
app.get('/', function(req, res) {
res.send('跨域内容哈哈哈,拿不到拿不到~');
});
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
# 解决方案
# JSONP
思想
- 同源策略限制的是ajax异步请求,并没有限制内嵌的script代码,所以可以利用这一点去取对应的数据
- 前端和后端约定好回调函数名称,然后前端创建script标签向后端指定url发起get请求,后端将回调函数执行且传入希望有的数据,前端接受到数据由于是script标签,就会开始执行,拿到数据且执行出希望的结果。
- 限制:只能请求get数据,因为是script标签发起的请求
- 限制:需要约定好回调函数名,较为不方便
- 和 跨域数据服务器1,约定好回调函数叫【fn】
- 跨域数据服务器1,返回【fn执行代码块】,在浏览器端执行
const express = require('express');
const app = new express();
app.listen(8888,() => {
console.log('listen 8888');
});
app.get('/', (req, res) => res.send(`
<body>
<div>首页</div>
<script>
function fn(str) {
console.log(str);
}
setTimeout(() => {
const script = document.createElement('script');
script.src = 'http://localhost:8889'
document.body.appendChild(script); // 我们约定好的兄弟~
}, 2000);
</script>
</body>
`));
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
const express = require('express');
const app = new express();
app.listen(8889, () => {
console.log('listen 8889');
});
app.get('/', function(req, res) {
res.send('fn("我们约定好的兄弟~")');
});
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
- 向跨域数据服务器2,取用户数据,约定好回调函数为【getUser】
const express = require('express');
const app = new express();
app.listen(8888,() => {
console.log('listen 8888');
});
app.get('/', (req, res) => res.send(`
<body>
<div>首页</div>
<script>
const name = 'hdy'
let user;
function getUser(userData) {
user = userData;
}
setTimeout(() => {
const script = document.createElement('script');
script.src = 'http://localhost:8889?name=' + name
document.body.appendChild(script);
// 请求回来后本地user读取正确
setTimeout(() => console.log(user), 2000);
// {"name":"hdy","age":18,"major":"软件工程"}
}, 2000);
</script>
</body>
`));
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
- database对象模拟数据库数据
const express = require('express');
const app = new express();
app.listen(8889, () => {
console.log('listen 8889');
});
const database = {
hdy: {
name: 'hdy',
age: 18,
major: '软件工程'
},
zs: {
name: '张三',
age: 20,
major: '法律'
}
}
app.get('/', function(req, res) {
const name = req.query.name;
const data = database[name] ? JSON.stringify(database[name]) : '{}';
// 入参到前端会直接解析成对象,因为这是script标签返回内容字段
res.send(`getUser(${data})`);
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# CORS
- 全称:Cross-origin resource sharing
- 服务器同意跨域的资源请求,并在响应头上添加对应字段
- 响应头设置的字段:
字段 | 值 |
---|---|
Access-Control-Allow-Origin | 【必须】允许请求的源域名,url | * |
Access-Control-Request-Method | 可选,支持跨域的方法 |
Access-Control-Expose-Headers | 可选,允许包含的请求头 |
Access-Control-Allow-Credentials | 可选,布尔值,headers是否接收cookie字段 |
Access-Control-Max-Age | 可选,本次预检请求有效期 |
- fetch默认方式是get,直接去向资源服务器拿数据
- 请求能不能回来就看服务器的响应头【Access-Control-Allow-Origin】字段有没有我这个服务器
const express = require('express');
const app = new express();
app.listen(8888,() => {
console.log('listen 8888');
});
app.get('/', (req, res) => res.send(`
<body>
<div>首页</div>
<script>
let user;
const name = 'hdy'
setTimeout(() => {
fetch('http://localhost:8889?name=' + name)
.then(res => res.json())
.then(res => {
user = res;
console.log(user);
// {name: 'hdy', age: 18, major: '软件工程'}
})
}, 2000);
</script>
</body>
`));
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
const express = require('express');
const app = new express();
app.listen(8889, () => {
console.log('listen 8889');
});
// 允许跨域中间件
const allowCors = function(req, res, next) {
res.header('Access-Control-Allow-Origin', req.headers.origin);
res.header('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE,OPTIONS');
res.header('Access-Control-Allow-Headers', 'Content-Type');
res.header('Access-Control-Allow-Credentials','true');
next();
};
app.use(allowCors); // 使用跨域中间件
// 模拟数据库
const database = {
hdy: {
name: 'hdy',
age: 18,
major: '软件工程'
},
zs: {
name: '张三',
age: 20,
major: '法律'
}
}
app.get('/', function(req, res) {
const name = req.query.name;
const data = database[name] ? JSON.stringify(database[name]) : '{}';
res.send(data);
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
- 以post的形式向服务器发送请求
const express = require('express');
const app = new express();
app.listen(8888,() => {
console.log('listen 8888');
});
app.get('/', (req, res) => res.send(`
<body>
<div>首页</div>
<script>
let user;
setTimeout(() => {
fetch('http://localhost:8889', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({name: 'zs'})
})
.then(res => res.json())
.then(res => {
user = res;
console.log(user);
// {name: '张三', age: 20, major: '法律'}
})
}, 2000);
</script>
</body>
`));
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
- express解析请求体【req.body】需要【body-parser】这个库
const express = require('express');
const app = new express();
app.listen(8889, () => {
console.log('listen 8889');
});
// 自定义跨域中间件
const allowCors = function(req, res, next) {
res.header('Access-Control-Allow-Origin', req.headers.origin);
res.header('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE,OPTIONS');
res.header('Access-Control-Allow-Headers', 'Content-Type');
res.header('Access-Control-Allow-Credentials','true');
next();
};
app.use(allowCors); // 使用跨域中间件
const database = {
hdy: {
name: 'hdy',
age: 18,
major: '软件工程'
},
zs: {
name: '张三',
age: 20,
major: '法律'
}
}
const bdParser = require('body-parser');
app.use(bdParser.urlencoded({extended: false}));
app.use(bdParser.json());
app.post('/', function(req, res) {
const name = req.body.name;
const data = database[name] ? JSON.stringify(database[name]) : '{}';
res.send(data);
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
- 跨域传cookie需要同时满足三个条件:
- Access-Control-Allow-Origin为非 *
- Access-Control-Allow-Credentials为true
- 请求设置credentials
- 请求带去同源服务器,同源服务器拿到user字段,响应头增加cookie,再重定向到跨域服务器要数据
- 首先默认允许跨域,然后还要允许【credentials】,重定向要跟随也要特殊设置【redirect】
const express = require('express');
const app = new express();
app.listen(8888,() => {
console.log('listen 8888');
});
app.get('/', (req, res) => res.send(`
<body>
<div>首页</div>
<script>
let user;
setTimeout(() => {
fetch('http://localhost:8888/data', {
credentials: 'include',
redirect: 'follow'
})
.then(res => res.json())
.then(res => {
user = res;
console.log(user);
// {name: '张三', age: 20, major: '法律'}
})
}, 2000);
</script>
</body>
`));
app.get('/data' , (req, res) => {
res.cookie('user', 'zs');
res.redirect('http://localhost:8889');
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
- express解析cookie需要【cookie-parser】
const express = require('express');
const app = new express();
app.listen(8889, () => {
console.log('listen 8889');
});
// 自定义跨域中间件
const allowCors = function(req, res, next) {
res.header('Access-Control-Allow-Origin', req.headers.origin);
res.header('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE,OPTIONS');
res.header('Access-Control-Allow-Headers', 'Content-Type');
res.header('Access-Control-Allow-Credentials','true');
next();
};
app.use(allowCors); // 使用跨域中间件
const database = {
hdy: {
name: 'hdy',
age: 18,
major: '软件工程'
},
zs: {
name: '张三',
age: 20,
major: '法律'
}
}
const ckParser = require('cookie-parser');
app.use(ckParser());
app.get('/', function(req, res) {
const user = req.cookies.user;
const data = database[user] ? JSON.stringify(database[user]) : '{}';
res.send(data);
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# node代理
思路
- 同源限制是浏览器规定的
- 不限制其他平台的网络请求
- 那么就让服务器代理发送请求,再把数据还给浏览器端,浏览器就不知道这是跨域的资源了
- 浏览器还是向源服务器发送请求,源服务器去发跨域请求。取回数据给浏览器
- 代理的情况下不需要跨域的服务器做什么,因为他也不知道这是浏览器的跨域请求
const express = require('express');
const app = new express();
app.listen(8889, () => {
console.log('listen 8889');
});
// 模拟数据库
const database = {
hdy: {
name: 'hdy',
age: 18,
major: '软件工程'
},
zs: {
name: '张三',
age: 20,
major: '法律'
}
}
app.get('/', function(req, res) {
new Promise(resolve => {
setTimeout(() => {
// 异步:假装去数据库读取
const name = req.query.name;
const data = database[name] ? JSON.stringify(database[name]) : '{}';
resolve(data);
}, 1000);
}).then(data => {
res.send(data);
})
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
- express代理有封装好的【express-http-proxy】库
const express = require('express');
const app = new express();
app.listen(8888,() => {
console.log('listen 8888');
});
app.get('/', (req, res) => res.send(`
<body>
<div>首页</div>
<script>
let user;
const name = 'hdy'
setTimeout(() => {
fetch('http://localhost:8888/proxy?name=' + name)
.then(res => res.json())
.then(res => {
user = res;
console.log(user);
// {name: 'hdy', age: 18, major: '软件工程'}
})
}, 2000);
</script>
</body>
`));
const proxy = require('express-http-proxy');
app.use('/proxy', proxy('http://localhost:8889'));
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
- 开发环境下
- vue.config.js配置
// vue项目开发模式中,所有【/api】开头的请求路径都使用node代理请求,再把数据还给浏览器
module.exports = {
devServer: {
proxy: {
'/api': {
target: 'http://localhost:8889',
changeOrigin: true, // 跨域
pathRewrite: {
'^/api': '/'
}
}
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
- 跨域请求vue文件
<template>
<div>
<div>{{data}}</div>
<button @click='getData'>获取跨域数据</button>
</div>
</template>
<script>
export default {
data() {
return {
data: '默认数据'
}
},
methods: {
getData() {
fetch('/api?name=zs')
.then(res => res.json())
.then(res => this.data = res);
}
}
}
</script>
<style>
</style>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
- 安装插件
npm i http-proxy-middleware
1
- src目录下创建文件,
名字固定
,react脚手架会调用 - 可以创建多个代理
需要cookies
// 注:不能改名,不能换 .ts
// src/setupProxy.js
const paoxy = require("http-proxy-middleware");
// 代理服务器存储cookie,下次请求附带cookie
let cookie;
const relayRequestHeaders = (proxyReq, req) => {
if (cookie) {
proxyReq.setHeader("cookie", cookie);
}
};
const relayResponseHeaders = (proxyRes, req, res) => {
const proxyCookie = proxyRes.headers["set-cookie"];
if (proxyCookie) {
cookie = proxyCookie;
}
};
module.exports = function (app) {
app.use(
paoxy.createProxyMiddleware("/dev", {
target: "http://localhost:8080",
changeOrigin: true,
cookieDomainRewrite: "localhost",
pathRewrite: { "^/dev": "" },
onProxyReq: relayRequestHeaders,
onProxyRes: relayResponseHeaders,
})
);
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
不需要cookies 配置
// src/setupProxy.js
const proxy = require('http-proxy-middleware')
module.exports = function(app) {
app.use(
proxy.createProxyMiddleware('/api1', {
target: 'http://localhost:8888',
changeOrigin: true,
pathRewrite: {'^/api1': ''}
})
)
}
1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
# Nginx
- 原理和proxy代理一样,只是vue的proxy配置只有开发环境生效,生产环境一般用nginx
- nginx.conf
server {
# ...
# 源页面路径
location / {
root /Users/huangdeyu/Documents/code/00test/test;
index test.html;
}
# 代理服务器路径
location /api {
proxy_pass http://localhost:8889;
}
# ...
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
- 执行命令
# 启动
nginx
# 重载配置文件
nginx -s reload
1
2
3
4
5
2
3
4
5
- 还是向源服务器发起同源请求,nginx处理跨域请求
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<div>首页</div>
<script>
let user;
const name = 'hdy'
setTimeout(() => {
fetch('/api?name=' + name)
.then(res => res.json())
.then(res => {
user = res;
console.log(user);
// {name: 'hdy', age: 18, major: '软件工程'}
})
}, 2000);
</script>
</body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
- 注:nginx配置代理默认会带上路径,例:【/api】
const express = require('express');
const app = new express();
app.listen(8889, () => {
console.log('listen 8889');
});
// 模拟数据库
const database = {
hdy: {
name: 'hdy',
age: 18,
major: '软件工程'
},
zs: {
name: '张三',
age: 20,
major: '法律'
}
}
app.get('/api', function(req, res) {
new Promise(resolve => {
setTimeout(() => {
// 异步:假装去数据库读取
const name = req.query.name;
const data = database[name] ? JSON.stringify(database[name]) : JSON.stringify({msg: "未查到此人"});
resolve(data);
}, 1000);
}).then(data => {
res.send(data);
})
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# webSocket
定义
- 规范定义了一种 API,可在网络浏览器和服务器之间建立“套接字”连接。简单地说:客户端和服务器之间存在持久的连接,而且双方都可以随时开始发送数据。
- 跨域原理:本质上是浏览器和服务器建立了持久链接,并不是传统的http请求,所以并没有跨域限制
const express = require('express');
const app = new express();
app.listen(8888,() => {
console.log('listen 8888');
});
app.get('/', (req, res) => res.send(`
<body>
<div>首页</div>
<script>
let user;
let socket = new WebSocket("ws://localhost:8889");
socket.onopen = function() {
console.log('用websocket与服务器建立连接...');
};
socket.onmessage = function(e) {
user = JSON.parse(e.data);
};
setTimeout(() => {
socket.send('zs');
setTimeout(() => console.log(user), 1000);
}, 2000);
</script>
</body>
`));
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
- node服务器可用的webSocket库有:
- ws
- Socket.IO
- WebSocket-Node
- 这里用ws库
// 模拟数据库
const database = {
hdy: {
name: 'hdy',
age: 18,
major: '软件工程'
},
zs: {
name: '张三',
age: 20,
major: '法律'
}
}
const ws = require('ws');
const wsServer = new ws.Server({ port: 8889 });
wsServer.on('connection', function (ws) {
console.log('已和客户端持久连接');
ws.on('message', function (message) {
const name = message.toString();
const user = database[name] ? JSON.stringify(database[name]) : JSON.stringify({msg: '查无此人'})
ws.send(user);
});
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 谷歌浏览器关闭跨域
- 桌面新建文件夹
aaa
- 注意路径
pwd open -n /Applications/Google\ Chrome.app/ --args --disable-web-security --user-data-dir=/Users/dreamarts/Desktop/aaa
1
2
# 其他方案
其他不常用方案
方案 | 适用 |
---|---|
window.postMessage | 可以向浏览器的其他窗口或内嵌iframe进行跨域通信 |
document.domain + iframe | 可以让同一个基础域名的网页进行相互操作,如:【id.qq.com】和【qq.com】可以互相嵌套iframe进行跨域通信操作,只要把他们都设置【document.domain = 'qq.com'】 |
浏览器关闭跨域限制 | 启动增加参数【--disable-web-security --user-data-dir】 |
- iframe嵌套的是跨域的网站时,会隔离外层DOM的事件,也就是说外层的事件传不进DOM去。例如iframe内部点击,不会传播到iframe外层的div上。
# 跨站
- 跨站路径不完全相同:
a.b.com
/c.b.com
/b.com
- 服务器设置相应头
SameSite=None;Secure
app1.get('/',(req,res)=>{
res.setHeader('Set-Cookie','SameSite=None;Secure')
res.send('ok')
})
1
2
3
4
2
3
4