九种跨域解决方案
同源策略:
官方解释:同源策略是一个重要的安全策略,它用于限制一个origin的文档或者它加载的脚本如何能与另一个源的资源进行交互。它能帮助阻隔恶意文档,减少可能被攻击的媒介。
描述:例如:https://www.baidu.com https是协议,com为顶级域名,baidu是一级域名,www是主机名,必须协议(https)、域名(www.baidu.com)、端口号(8000)一样才能相互访问。
解决:
1.jsonp
定义:Jsonp(JSON with Padding) 是 json 的一种"使用模式",可以让网页从别的域名(网站)那获取资料,即跨域读取数据,其实就是前端定义一个回调函数告诉后端这个函数,请求后端时候后端执行这个函数,返回数据放在函数参数里面带回给前端。
实现如下:(端口号不一样实现的跨域)
node.js 服务器端的代码:
const express = require('express') const app = express() const port = 3000 app.get('/', (req, res) => { let query = req.query let cb =query.cb res.send(`${cb}("我是服务器返回来的数据")`) }) app.listen(port, () => { console.log(`Example app listening at http://localhost:${port}`) })
客户端代码:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <h1>jsonp</h1> <script> function cb(data){ console.log(data)//我是服务器返回来的数据
} </script> <script src="http://127.0.0.1:3000/?cb=cb"></script> </body> </html>
优缺点:只能发送get请求,不支持post,put,delete;不安全xss攻击,一般不采用
2.cors
定义:CORS是一个W3C标准,全称是"跨域资源共享"(Cross-origin resource sharing),它允许浏览器向跨源服务器,发出XMLHttpRequest
请求,从而克服了AJAX只能同源使用的限制,可以参考阮老师的仔细讲解(http://www.ruanyifeng.com/blog/2016/04/cors.html)
实现如下:
服务端代码:
const express = require('express') const app = express() const port = 4000 let whitList = ["http://localhost:3000"] app.use(function (req, res, next) { let origin = req.headers.origin; if (whitList.includes(origin)) { // 设置允许的请求源 res.setHeader("Access-Control-Allow-Origin", origin) //设置允许的请求头参数 res.setHeader("Access-Control-Allow-Headers","name") //设置允许的请求方法,默认get和post不需要配置 res.setHeader("Access-Control-Allow-Methods","PUT") //预检不处理 if(res.method=="OPTIONS"){ res.end() } //预检检测时间(最大存活时间) res.setHeader("Access-Control-Allow-Max-Age",6) //设置允许带上cookie res.setHeader("Access-Control-Allow-Credentials",true) //设置允许获取的响应头参数 res.setHeader("Access-Control-Expose-Headers",'age') } next() }) app.get('/getData', (req, res) => { res.send(`get请求,我是服务器4000返回来的数据`) }) app.put('/getData', (req, res) => { res.setHeader("age",20) res.send(`put请求,我是服务器4000返回来的数据`) }) app.listen(port, () => { console.log(`Example app listening at http://localhost:${port}`) })
前端代码:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <h1>cors</h1> <script> let xhr = new XMLHttpRequest; xhr.open("PUT","http://localhost:4000/getData",true), //设置请求头 xhr.setRequestHeader("name","jituitaixiao") //设置cookie document.cookie = 'name=jitiu' //强制带上cookie,默认不带上 xhr.withCredentials = true xhr.onreadystatechange = function(){ if(xhr.readyState==4){ if(xhr.status>=200&&xhr.status<300||xhr.status===304){ console.log(xhr.response) //获取响应头的参数 console.log(xhr.getResponseHeader("age")) } } } xhr.send() </script> </body> </html>
优缺点:相比jsonp功能更加强大和安全,但是后端代码设置头部比较麻烦。
3.postMessage
定义:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <h1>a.html</h1> <iframe src="http://localhost:4000/b.html" frameborder="0" id="iframe" onload="load()"></iframe> <script> //像b页面发送数据 function load(){ let iframe = document.getElementById("iframe"); iframe.contentWindow.postMessage("a页面数据","http://localhost:4000") // 接收b页面数据 window.onmessage = function(e){ console.log(e.data) } } </script> </body> </html>
b.html页面,用node服务器启动本地到4000端口:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <h1>b.html</h1> <script> //接收b页面数据 window.onmessage = function(e){ console.log(e.data) //像a页面发送数据 e.source.postMessage("b页面数据",e.origin) } </script> </body> </html>
把b页面通过iframe标签放在a页面,通过postMessage和onmessage相互通讯。
优缺点:html5引入的message的API可以更方便、有效、安全的解决这些难题。postMessage()方法允许来自不同源的脚本采用异步方式进行有限的通信,可以实现跨文本档、多窗口、跨域消息传递。
4.window.name
定义:下面代码有3个html页面,a页面和b页面同域,c页面独立,第一步,使用a页面的iframe标签的src属性指向c页面地址,此时不做处理就是跨域请求,会报错;第二步,在a页面的iframe标签上面定义一个onload方法,他的作用是将src的地址改成b页面地址,a和b页面属于同源,可以进行通信。开始在a页面请求c的时候,c页面抛出一个window.name,这个方法在后来a页面访问b的时候仍然存在,不会消失!这样a页面就拿到了c页面数据,实现了跨域!代码如下:
a.html(在本地服务的3000端口)
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <iframe src="http://localhost:4000/c.html" frameborder="0" id="iframe" onload="onload"></iframe> <script> let first = true;//这个变量设置作用是修改src后页面从新渲染,会从新执行onload函数,防止死循环 function onload(){ if(first){ let iframe = document.getElementById("iframe") iframe.scr = "http://localhost:3000/b.html" first = false }else{ console.log(iframe.contentWindow.name)//"鸡腿太小" } } </script> </body> </html>
b.html(在本地服务的3000端口,不做任何处理)
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> </body> </html>
c.html(在本地服务的4000端口)
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <script> window.name = "鸡腿太小" </script> </body> </html>
优缺点:用的很少,有点low。。。
5.hash
定义:和window.name一样,三个页面a.html、b.html、c.html ,a页面和b页面同域,c为独立的域名,a页面通过iframe的src带一个hash值传递给c页面,在c页面通过获取hash,然后创建一个iframe,src待hash传给b页面,a页面可以通过监听b页面hash变化获取c的hash值
a.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <!-- a页面传递过一个hash值给c页面 --> <iframe src="http://localhost:4000/c.html#jituitaixiao" frameborder="0" id="iframe" onload="onload"></iframe> <script> // b页面修改了hash值,a页面监听获取hash值 window.onhashchange = function (){ console.log(location.hash)//jituizhendetaixioale } </script> </body> </html>
b.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <script> //将c页面传递过来得hash值传给3000端口 window.parent.parent.location.hash=location.hash </script> </body> </html>
c.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <script> //获取a页面传递过来得hash值 console.log(location.hash)//jituitaixioa //通过iframe向b页面传递hash let iframe = document.createElement("iframe"); iframe.src = "http://localhost:3000/b.html#jituizhendetaixiaole" document.body.appendChild(iframe) </script> </body> </html>
6.document.domain
定义:首先用document.domain来指定域,是可以的,但是有局限性,也就是一级域名一致才可以。如下:
www.sojson.com
下指到sojson.com
是可以的。
icp.sojson.com
下指到 sojson.com
是可以的。
像上面是可以的,因为 一级域名 都是 sojson.com
。
www.sojson.com
下指到 www.baidu.com
是不行的。
sojson.com
指到 baidu.com
还是不行的。
比如我们要在当前页面下,“www.sojson.com/shiro”
下上传图片到 "cdn.sojson.com/images/"
下去。直接搞肯定是不行的。
在请求“www.sojson.com/shiro”和
"cdn.sojson.com/images/"的时候,都加上如下代码:
document.domain = "sojson.com"
7.webSocket
定义:它的最大特点就是,服务器可以主动向客户端推送信息,客户端也可以主动向服务器发送信息,是真正的双向平等对话,属于服务器推送技术的一种。
其他特点包括:
(1)建立在 TCP 协议之上,服务器端的实现比较容易。
(2)与 HTTP 协议有着良好的兼容性。默认端口也是80和443,并且握手阶段采用 HTTP 协议,因此握手时不容易屏蔽,能通过各种 HTTP 代理服务器。
(3)数据格式比较轻量,性能开销小,通信高效。
(4)可以发送文本,也可以发送二进制数据。
(5)没有同源限制,客户端可以与任意服务器通信。
(6)协议标识符是ws
(如果加密,则为wss
),服务器网址就是 URL。
客户端代码如下:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <h1>WebSocket</h1> <body> <script> // 创建一个3000长链接端口 let socket = new WebSocket("ws://localhost:3000") //打开服务器,发送请求数据 socket.onopen = function () { socket.send("jituitaixioa") } //接收webSocket返回来的数据 socket.onmessage = function (evt) { console.log("Received Message: " + evt.data); socket.close(); }; //关闭长链接后的回调函数 socket.onclose = function (evt) { console.log("Connection closed."); } //连接错误时候的回调函数 socket.onerror = function (event) { console.log("Connection error"); }; </script> </body> </html>
服务端代码如下:
let WebSocket = require("ws"); let wss = new WebSocket.Server({port:3000}); wss.on("connection",function(ws){ ws.on("message",function(data){ console.log(data) ws.send("jituzhendetaixiaole") }) })
8.nginx后端配置即可
9.proxy反向代理
感谢各位支持