## 解决跨域的方法
在讲跨域之前,应该先了解一下浏览器的同源策略
同源策略是浏览器之间的一种约定,是浏览器最基本和最核心的安全策略,如果没有同源策略,网站很容易受到XSS和CSRF等攻击。
同源策略就是指网站:协议+域名+端口号三者相同
在同源策略下限制的内容:cookie,localStorage,sessionStorage,ajax请求
有三个标签允许跨域加载资源
常见跨域场景
当协议、子域名、主域名、端口号中任意一个不相同时,都算作不同域。不同域之间相互请求资源,就算作“跨域”。
注意:
- 如果是因为协议造成的跨域(http和https),前端是解决不了的,愉快的丢给后端人员解决
- 在跨域问题上,仅仅是通过“URL的首部”来识别而不会根据域名对应的IP地址是否相同来判断。“URL的首部”可以理解为“协议, 域名和端口必须匹配”
解决跨域的方式
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) 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的优缺点:
- 优点:简单兼容性好
- 缺点:仅支持get请求,有局限性,可能会有XSS攻击
2.cors
服务端设置 Access-Control-Allow-Origin 就可以开启 CORS。 该属性表示哪些域名可以访问资源,如果设置通配符则表示所有网站都可以访问资源。
虽然设置 CORS 和前端没什么关系,但是通过这种方式解决跨域问题的话,会在发送请求时出现两种情况,分别为简单请求和复杂请求。
简单请求
满足以下条件的为简单请求
- 请求方法为get,head,post
- 请求头中的
Content-Type
是text/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; 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端口已启用") })
|
- 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>
|
websocket
Websocket是HTML5的一个持久化的协议,它实现了浏览器与服务器的全双工通信,同时也是跨域的一种解决方案。WebSocket和HTTP都是应用层协议,都基于 TCP 协议。但是 WebSocket 是一种双向通信协议,在建立连接之后,WebSocket 的 server 与 client 都能主动向对方发送或接收数据。同时,WebSocket 在建立连接时需要借助 HTTP 协议,连接建立好了之后 client 与 server 之间的双向通信就与 HTTP 无关了。
websocket的实例就放我以前写的一个聊天室
多人聊天室实例
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) => { 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 function load() { if(first){ let iframe = document.getElementById('iframe'); iframe.src = 'http://127.0.0.1:5500/window.name的b页面.html'; first = false; }else{ 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>
|