title: 解决跨域的方法
date: 2020-11-16 13:11:11
tags:
- 前端
- 笔记
categories: 前端

## 解决跨域的方法

在讲跨域之前,应该先了解一下浏览器的同源策略

同源策略是浏览器之间的一种约定,是浏览器最基本和最核心的安全策略,如果没有同源策略,网站很容易受到XSS和CSRF等攻击。

同源策略就是指网站:协议+域名+端口号三者相同

在同源策略下限制的内容:cookie,localStorage,sessionStorage,ajax请求

有三个标签允许跨域加载资源

  • <img>
  • <link>
  • <srript>
常见跨域场景

当协议、子域名、主域名、端口号中任意一个不相同时,都算作不同域。不同域之间相互请求资源,就算作“跨域”。

注意:

  1. 如果是因为协议造成的跨域(http和https),前端是解决不了的,愉快的丢给后端人员解决
  2. 在跨域问题上,仅仅是通过“URL的首部”来识别而不会根据域名对应的IP地址是否相同来判断。“URL的首部”可以理解为“协议, 域名和端口必须匹配”

解决跨域的方式

  1. JSONP

    JSONP利用了<script>可以跨域加载资源,可以从其他网站动态获得json数据。JSONP请求要请求服务器要支持才可以

    在一下常见的网站,如bilibili,爱奇艺,腾讯视频,他们首页的搜索接口都是可以支持jsonp请求的

    实例

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
39
40
41
42
43
44
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>

<body>
<input type="text" id="search">
<button id="button">搜索</button>
<script>
(() => {
// 获取搜索关键字
let button = document.getElementById("button");
let search = document.getElementById("search");
button.onclick = function () {
let body = document.body
let word = search.value;
console.log(search)
console.log(word)
// 动态创建一个script标签
let scriptTag = document.createElement('script');
scriptTag.setAttribute('src',
`https://s.search.bilibili.com/main/suggest?func=suggest&suggest_type=accurate&sub_type=tag&main_ver=v1&highlight=&userid=3730600&bangumi_acc_num=1&special_acc_num=1&topic_acc_num=1&upuser_acc_num=3&tag_num=10&special_num=10&bangumi_num=10&upuser_num=3&term=${word}&rnd=0.428375950382754&jsonp=jsonp&callback=myCallBack`
)
document.body.appendChild(scriptTag);
}

})()
</script>
<script>
function myCallBack(e) {
console.log(e)
// 创建一个列表
for(let i = 0; i < e.result.tag.length; i++){
console.log(e.result.tag[i].value)
}
}
</script>
</body>

</html>

在这里插入图片描述

JSONP的优缺点:

  1. 优点:简单兼容性好
  2. 缺点:仅支持get请求,有局限性,可能会有XSS攻击

2.cors

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

虽然设置 CORS 和前端没什么关系,但是通过这种方式解决跨域问题的话,会在发送请求时出现两种情况,分别为简单请求复杂请求

简单请求

满足以下条件的为简单请求

  1. 请求方法为get,head,post
  2. 请求头中的Content-Typetext/plain,mutipart/form-data,application/x-www-form-urlcoded
复杂请求

不属于简单请求的就是复杂请求

实例

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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="https://cdn.staticfile.org/jquery/1.10.2/jquery.min.js"></script>
</head>
<body>
<script>
$.ajax({
method:'get',
url: "http://localhost:3000/simpleGet",
success: function (response) {
console.log(response)
}
});
$.ajax({
method: "put",
url: "http://localhost:3000/simplePut",
success: function (response) {
console.log(response)
}
});
</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
27
28
29
let express = require("express");
let app = new express();

// 配置白名单
const whiteAdress = ["http://127.0.0.1:5500"]

app.use(function(req,res,next){
let orgin = req.headers.origin;
// console.log(req.headers)
if(whiteAdress.includes(orgin)){
res.setHeader("Access-Control-Allow-Origin", orgin)
res.setHeader('Access-Control-Allow-Methods', 'PUT')
if(req.method == "option"){
res.end(200,"我是简单请求");
}
}
next();
})

app.get("/simpleGet", function(req, res){
res.send("我是简单请求")
})
app.put("/simplePut",function(req,res){
res.end("我是put请求")
})

app.listen(3000, ()=> {
console.log("3000端口已启用")
})

在这里插入图片描述

  1. postMessage

为数不多的可以跨域操作window属性之一可以解决

  • 页面和其他新开窗口的数据传递
  • 多窗口消息传递
  • 页面嵌套iframe消息传递
  • 上面三个场景跨域数据传递

postMessage()允许来自不同源的脚本采用异步方式进行有限的通信,该方法由三个参数

  • message:发送的数据
  • targetOrigin:发送地址的URL
  • transfer:是一串和message 同时传递的 Transferable 对象. 这些对象的所有权将被转移给消息的接收方,而发送一方将不再保有所有权。

实例
a.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
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>

<body>
<iframe src="http://127.0.0.1:4000/" frameborder="0" id="frame" onload="load()"></iframe> //等它加载完触发一个事件
<script>
function load(){
let frame = document.getElementById('frame')

frame.contentWindow.postMessage("我是a页面", "http://127.0.0.1:4000/")
window.onmessage = function (e) { //接受返回数据
console.log(e.data)
}
}
</script>
</body>

</html>

b页面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<h1>我是b页面</h1>
<script>
(() => {
window.onmessage = function(e) {
console.log(e.data);
e.source.postMessage("我是b页面","http://127.0.0.1:5500/postMessage跨域a.html")
}
})()
</script>
</body>
</html>

在这里插入图片描述

  1. websocket
    Websocket是HTML5的一个持久化的协议,它实现了浏览器与服务器的全双工通信,同时也是跨域的一种解决方案。WebSocket和HTTP都是应用层协议,都基于 TCP 协议。但是 WebSocket 是一种双向通信协议,在建立连接之后,WebSocket 的 server 与 client 都能主动向对方发送或接收数据。同时,WebSocket 在建立连接时需要借助 HTTP 协议,连接建立好了之后 client 与 server 之间的双向通信就与 HTTP 无关了。
    websocket的实例就放我以前写的一个聊天室
    多人聊天室实例

  2. Node中间件代理(两次跨域)
    实现原理:同源策略是浏览器需要遵循的标准,而如果是服务器向服务器请求就无需遵循同源策略。

实例
a.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
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js"></script>
<script>
$.ajax({
url: 'http://localhost:3000',
type: 'post',
data: { name: 'larmy', password: '123456' },
contentType: 'application/json;charset=utf-8',
success: function(result) {
console.log(result)
},
error: function(msg) {
console.log(msg)
}
})
</script>
</body>
</html>

a.js

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
const http = require('http')
// 第一步:接受客户端请求
const server = http.createServer((request, response) => {
// 代理服务器,直接和浏览器直接交互,需要设置CORS 的首部字段
response.writeHead(200, {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': '*',
'Access-Control-Allow-Headers': 'Content-Type'
})
// 第二步:将请求转发给服务器
const proxyRequest = http
.request(
{
host: '127.0.0.1',
port: 4000,
url: '/',
method: request.method,
headers: request.headers
},
serverResponse => {
// 第三步:收到服务器的响应
var body = ''
serverResponse.on('data', chunk => {
body += chunk
})
serverResponse.on('end', () => {
console.log('The data is ' + body)
// 第四步:将响应结果转发给浏览器
response.end(body)
})
}
)
.end()
})
server.listen(3000, () => {
console.log('The proxyServer is running at http://localhost:3000')
})
1
2
3
4
5
6
7
8
9
10
const http = require('http')
const data = { title: 'larmy', password: '123456' }
const server = http.createServer((request, response) => {
if (request.url === '/') {
response.end(JSON.stringify(data))
}
})
server.listen(4000, () => {
console.log('The server is running at http://localhost:4000')
})

6.nginx
这个还不是很了解

7.window.name + iframe
window.name属性的独特之处:name值在不同的页面(甚至不同域名)加载后依旧存在,并且可以支持非常长的 name 值(2MB)。

实例

a页面,放在5500端口

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
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<iframe src="http://127.0.0.1:4000/" frameborder="1" onload="load()" id="iframe"></iframe>
<script>
let first = true
// onload事件会触发2次,第1次加载跨域页,并留存数据于window.name
function load() {
if(first){
// 第1次onload(跨域页)成功后,切换到同域代理页面
let iframe = document.getElementById('iframe');
iframe.src = 'http://127.0.0.1:5500/window.name的b页面.html';
first = false;
}else{
// 第2次onload(同域b.html页)成功后,读取同域window.name中数据
console.log(iframe.contentWindow.name);
}
}
</script>
</body>
</html>

b页面,放在5500端口

1
2
3
4
5
6
7
8
9
10
11
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>

</body>
</html>

c页面,放在4000端口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<h1>c页面</h1>
<script>
window.name = '我是c页面'
</script>
</body>
</html>