同源策略
什么是同源策略 : 协议 、 域名 、 端口 一致
- 协议是什么? 如 : http 、https
- 域名是什么? 如 : baidu.com 、 juejin.cn 、 127.0.0.1 ...
- 端口号是什么 ? 域名或IP地址冒号后边
当协议、或域名 、或端口 不一样时 . 即跨域
为什么浏览器不支持跨域
- cookie 、 LocalStorage 防止被窃取
- DOM元素 防止被修改
- ajax 防止数据链接被盗用
实现跨域
- jsonp
- 在html标签中 img 、 script 标签的src属性不受同源策略影响,随意引用.
- jsonp是利用script标签的src属性引用跨域的js文件.并将回调函数写在引用中.跨域的js文件执行后,调用回调函数执行到页面中
- 缺点
- 只能get请求,不支持get/put/delete
- 容易被xss攻击
//我是个本地的页面
...
<script>
function callback(args){
console.log(args)
}
</script>
<script src="http://xxx.com/a.js?cb=callback"></script>
...
//我是http://xxx.com/a.js
...
function getQueryString(name) {
let reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)", "i");
let r = window.location.search.substr(1).match(reg);
if (r != null) {
return decodeURIComponent(r[2]);
};
return null;
}
var cbFun = getQueryString('cb') || null;
cbFun && cbFun([1,2,3])
...
- cors
- 主要是服务端做的操作,浏览器的请求已经发送到服务器,但被浏览器屏蔽.
// 以nodeJs express框架 解决为例
// 浏览器报错 : ‘Access-Control-Allow-Origin‘ header is ....
// 服务器中间件允许所有请求
...
app.use((req,res,next)=>{
res.setHeader('Access-Control-Allow-Origin',"*")
next()
})
...
// 服务器中间件设置指定可访问的源
...
const passOrigin = ["http://xxxx.com"]
app.use((req,res,nest)=>{
const headersOrigin = req.headers.origin;
if(passOrigin.includes(headersOrigin)){
res.setHeader('Access-Control-Allow-Origin',headersOrigin)
}
next()
})
...
// 浏览器报错 : ‘Access-Control-Allow-Headers‘
// 原因是浏览器发起 ajax 请求时 带有header头
// 客户端请求
$.ajax({
url:"http://xxx.com/xx",
type:"POST",
headers:{
a:1,
b:2
}
});
// 服务器中间件处理可接收任意请求头
...
app.use((req,res,next)=>{
res.setHeader('Access-Control-Allow-Headers',"*")
next()
})
...
//服务器中间件处理接收指定请求头 例: 接收请求头中的a 和 b
app.use((req,res,next)=>{
res.setHeader('Access-Control-Allow-Headers',"a,b")
next()
})
// 浏览器报错 : ‘Access-Control-Allow-Methods‘
// 原因: 客户端发送请求时 使用了PUT / DELETE 等方法, GET和POST 为默认方法 不会报错
// 客户端请求
$.ajax({
url:"http://xxx.com/xx",
type:"PUT",
data:{
a:1,
b:2
}
});
// 服务器中间件增加支持请求的方法
...
app.use((req,res,next)=>{
res.setHeader('Access-Control-Allow-Methods',"PUT,DELETE")
next()
})
// 设置完后, 客户端请求会发送2个
// 第一个请求的 Request Method: OPTIONS .是预测请求.用于试探服务器是否允许 PUT方法 可以跨域
// 此时服务器端中间件可以增加处理
app.use((req,res,next)=>{
res.setHeader('Access-Control-Allow-Methods',"PUT,DELETE")
res.setHeader('Access-Control-Max-Age',6); //预测最大通过时间.以秒为单位
if(req.method == "OPTIONS"){
res.end() // 如果是OPTIONS请求 不做任何处理
}
next()
})
...
// 浏览器报错 : ‘Access-Control-Allow-Credentials‘
// 原因 告知浏览器是否可以将对请求的响应暴露给前端
// 服务端中间件处理 , 允许携带cookie
app.use((req,res,next)=>{
res.setHeader('Access-Control-Allow-Credentials',true)
next()
})
// 浏览器报错 Refused to get unsafe header "xx"
// 原因是 服务端给客户端返回数据时 使用res.setHeader("xx","1"); 客户端想获取服务器返回的header中的数据
// 服务器中间件处理
app.use((req,res,next)=>{
//服务器端支持客户端获取返回header中xx字段 , 设置多个 实用","分隔
res.setHeader('Access-Control-Allow-Expose-Headders',"xx");
next()
})
- postMessage
- 用于iframe间页面通信
// a.html
<body>
<iframe src="http://xxx.com/b.html" id="frame" onload="onload()"></iframe>
</body>
<script>
function onload(){
const frame = document.getElementById("frame");
frame.contentWindow.postMessage("来自a.html的问候","http://xxx.com")
window.onmessage = function (e){
// 接收了b.html传过来的数据
console.log(e.data)
}
}
</script>
//b.html
<script>
window.onmessage = function(e){
// 接收了a.html传过来的数据
console.log(e.data)
// 给a.html回消息
e.source.postMessage("来自b.html的问候",e.origin)
}
<script>
- document.domain
- 仅限跨域的两个页面为一级域名和二级域名的关系
- 例如: xx.com 和 music.xx.com 这样的
// xx.com/a.html
<body>
<iframe src="music.baidu.com/b.html" id="frame" onload="load()"></iframe>
</body>
<script>
document.domain = "xx.com"
function load(){
const frame = document.getElementById("frame");
// 获取b.html中声明的a属性
console.log(frame.contentWindow.a) //100
}
</script>
// music.xx.com/b.html
<script>
document.domain = "xx.com"
var a = 100;
</script>
- window.name
- 浏览器默认的一个属性 , 属性默认值 “” 为空字符串
// a.html 和 b.html为同域 , c.html为跨域
// b.html为空页面
// http:xxx:3000/a.html
<body>
<iframe src="http:xxx:5000/c.html" onload="load()" id="frmae"></iframe>
</body>
<script>
var isFirst = true;
function load(){
const frmae = document.getElementById("frmae")
if(isFirst){
frmae.src = "http:xxx:3000/b.html";
isFirst = false;
}else{
console.log(frmae.contentWindow.name) // 这是c.html
}
}
</script>
// http:xxx:5000/c.html
<script>
window.name = "这是c.html"
</script>
- location.hash
- 网址路径后面的hash值可以用来通信
// http:xxx:3000/a.html
<body>
<iframe src="http:xxx:5000/c.html#from_a_html" id="frmae"></iframe>
</body>
<script>
window.onhashchange = function(){
console.log(location.hash) // from_c_html
}
</script>
// http:xxx:3000/b.html
<script>
// 将自己接收到的 hash值 传给上级引用的上级
window.parent.parent.location.hash = location.hash;
</script>
// http:xxx:5000/c.html
<script>
console.log(location.hash) // from_a_html
const iframeEl = document.createElement("iframe");
iframeEl.src = "http:xxx:3000/b.html#from_c_html";
document.body.appendChild(iframeEl)
</script>
- nginx
location / {
add_header Access-Control-Allow-Origin *;
add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS';
add_header Access-Control-Allow-Headers 'DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization';
if ($request_method = 'OPTIONS') {
return 204;
}
}
- websocket
- 使用socket.io
- 原理是借助socket服务 向跨域的页面中发送消息
// a.html
<script>
const socket = new WebSocket("ws://xxx:5000");
socket.onopen = function(){
socket.send("向服务器发送一个信息")
}
socket.onmessage = function(data){
console.log(data) // "来自服务器的消息"
}
</script>
// node server.js
const webSocket = require("ws");
const wss = new webSocket.Server({port:5000})
wss.on("connection",(ws)=>{
ws.on("message",(data)=>{
console.log(data); // "向服务器发送一个信息"
})
ws.send("来自服务器的消息")
})