同源策略
浏览器有同源策略,协议 域名 端口不同,就不能互相访问资源
实现跨域
- jsonp
- cors
- postMessage
- Window.name
- http-proxy
- Document.domain
JSONP
- 本地声明一个函数
function show(data){
console.log(data)
}
复制代码
- 后段返回该函数的执行
<script src="https://sp0.baidu.com/5a1Fazu8AA54nxGko9WTAnF6hhy/su?wd=lol&cb=show"></script>
// cb就是厚度返回的执行函数名
复制代码
//后端会返回
show({
p: false
q: "lol"
s: (10) ["lol官网", "lol手游", "lol转区", "lol是什么意思",
"lol半价吧", "lol春季赛", "lol转区系统"]
})
复制代码
封装一个通用jonsp函数
//使用
jsonp({
url:'xxx',
params:{wd:'xxx'},
cb:'xx'
}).then(res=>console.log(data))
复制代码
let jsonp = ({url,params,cb})=>{
return new Promise((resolve,reject)=>{
//1.创建一个script标签
let script = document.createElement('script')
//3. 将cb挂载到全局中,后端会返回cb(数据),接收数据返回数据即可
window[cb]=function(data){
resolve(data)
//4. 记得将增加到script删除
script.remove()
}
//2.将params和cb转化成wd=xxx&cb=xxx
params = {...params,cb}
let arr = []
for(let key in params){
arr.push(`${key}=${params[key]}`)
}
script.src=url+'?'+arr.join('&')
document.body.appendChild(script)
})
}
复制代码
jsonp的缺点
- 只能发送get请求,不支持post put delete
- 不安全,引用链接的网站如果返回了一段攻击代码,可能造成危害,xss攻击,不支持
node模拟下提供jsonp功能
let express = require('express')
let app = new express()
app.get('/say',function(req,res){
let {wd,cb}=req.query
res.end(`${cb}('返回的数据')`)
})
app.listen(3000,function(){console.log('启动')})
复制代码
cors
这是一个后端的解决方案,可以用node来模拟
启动两个服务器,一个3000端口,一个4000端口
3000端口启动一个网页,在网页中访问4000端口的资源
const express = require('express');
let app = express();
//将这个目录下的资源以静态资源的方式提供访问
app.use(express.static(__dirname));
app.listen(3000, function () {
console.log('3000启动');
});
复制代码
// index.html
<script>
let xhr = new XMLHttpRequest();
xhr.open('get', 'http://localhost:4000/say');
xhr.onreadystatechange = function () {
if(xhr.readyState === 4 && 300 >= xhr.status&&xhr.status >= 200) {
console.log(xhr.response);
}
};
xhr.send();
</script>
复制代码
浏览器输入:localhost:3000/index.html
,会警告跨域问题
并且端口:4000的服务是有接受到端口3000的请求的,只是返回的内容被浏览器屏蔽了
提示:Access-Control-Allow-Origin
没有允许http://localhost:3000
访问资源,可以
设置允许访问的域名
//设置允许访问的域名的白名单
let whiteList = ['http://localhost:3000'];
app.use(function (req, res, next) {
//获取请求的origin
let origin = req.headers.origin;
if (whiteList.includes(origin)) {
//如果在白名单内,则允许他访问
res.setHeader('Access-Control-Allow-Origin', origin);
}
next();
});
复制代码
设置允许访问的请求方式
但这种方式只允许get,post,head这种简单的请求,如果是put呢
xhr.open('put','http://localhost:3000/say')
复制代码
app.put('/say',function(req,res){
res.end('post请求')
})
复制代码
浏览器报错,我们需要后端设置允许put请求
app.use(function(req,res){
res.setHeader('Access-Control-Allow-Methods','PUT')//记得大写
})
复制代码
设置需要允许访问的请求头
如果我们在请求头中加点信息呢,
xhr.setRequestHeader('name','huzhiwu')
复制代码
浏览器警告说,需要后端允许那个请求头
但端口4000能接受到这个请求头
设置允许的请求头
res.setHeaders('Access-Control-Allow-Headers','name')
复制代码
设置max-age
我们在端口4000的服务中,打印每次请求的method
app.use(function(req,res){
console.log(req.method)
})
复制代码
刷新浏览器时,会发现。端口4000打印了两个method
options是浏览器在发现跨域时,会先向服务器一个预检请求,该方法返回允许访问的methods,如果请求方法不在methods中则报错,存在的话,就发送真正的请求
这样子每次发送一个请求,实际发送了两个请求,是比较耗性能的,如果我们的方法不经常改变,可以让
浏览器在一段时间内预检一次就够了
req.setHeader('Access-Control-Max-Age',5)//单位秒
复制代码
允许跨域携带cookie
let xhr = new XMLHttpRequest();
xhr.open('put', 'http://localhost:4000/say');
document.cookie='name=huzhiwu'
xhr.onreadystatechange = function () {
if (xhr.readyState === 4 && 300 >= xhr.status && xhr.status >= 200) {
console.log(xhr.response);
}
};
xhr.send();
复制代码
// 端口4000的服务器
app.use(function(req,res){
console.log(req.headers)
})
复制代码
发现没有我们携带的cookie
在ajax请求中增加
xhr.withCredentials = true;
复制代码
浏览器又报错了
在node中设置允许跨域携带cookie即可
res.serHeader('Access-Control-Allow-Credentials')
复制代码
允许浏览器获取服务器返回的headers
app.put('/say',function(req,res){
res.setHeader('name','huzhiwu')
res.end('put')
})
复制代码
let xhr = new XMLHttpRequest();
xhr.open('put', 'http://localhost:4000/say');
xhr.onreadystatechange = function () {
if (xhr.readyState === 4 && 300 >= xhr.status && xhr.status >= 200) {
console.log(xhr.getResponseHeader('name'));
}
};
xhr.send();
复制代码
浏览器又报错
浏览器觉得服务器返回给你的请求头不安全,不给你获取
在node中设置
res.setHeader('Access-Control-Expose-Headers','name')
复制代码
小结
//允许哪源可以访问我
res.setHeader('Access-Control-Allow-Origin',origin)
//允许那个方法可以访问我
res.setHeader('Access-Control-Allow-Methods','PUT')
//预检存活时间
res.setHeader('Access-Control-Max-Age',5)//单位秒
//允许请求携带的headers
res.setHeader('Access-Control-Allow-Headers','name')
//允许浏览器获取服务器返回的headers
res.setHeader('Access-Control-Expose-Headers','name')
复制代码
postMessage跨域
页面跟嵌套在iframe中的页面通信
分别开启a,b两个服务,端口号分别是3000,4000
//a.js b.js
const express = require('express');
const app = express();
app.use(express.static(__dirname));
app.listen(4000/3000, function () {
console.log('4000/3000启动');
});
复制代码
a页面放在3000端口,b页面放在4000端口
在b页面中引用a页面
<body>
<iframe src='http://localhost:3000/a.html' id='aIframe' onload='load()'>
</iframe>
<script>
function load(){
let frame = document.getElementById('aIframe')
//给嵌套在iframe中的页面的window发送信息
//第二个参数是origin
frame.contentWindow.postMessage('你好我是b页面','http://localhost:3000/a.html')
}
//b页面监听来自a页面的信息
window.onmessage=function(e){
console.log(e.data)
}
</script>
</body>
复制代码
在a页面中监听b页面的信息
window.onmessage=function(e){
console.log(e.data)
//收到信息后给b页面发送信息,
e.source.postMessage('你好,我是a页面',e.origin)
}
复制代码
浏览器打开http://localhost:4000/b.html
小结
发送信息
window.postMessage('信息',orign)
复制代码
接收信息
window.onmessage=function(e){
console.log(e.data)
}
复制代码
window.name跨域
window.name默认为空字符串,我们可以在name中存入信息,跨域访问他
- a和b是同域的
http://localhost:3000
- c是另一个域的
http://localhost:4000
- a页面先iframe引用c,再iframe引用b,
- a页面即可获取c页面的window.name
开启两个服务器,分别是3000和4000端口
c页面
<script>
window.name='我是c页面'
</sctript>
复制代码
a页面
<body>
<iframe src='http://localhost:4000/c.html' id='frame' onload='load()'>
</iframe>
<script>
let first=true
let frame = document.getElementById('frame')
function load(){
//第一次加载完成,立即将frame的src转到同源下
if(first){
frame.src='http://localhost:3000/b.html'
first=false;
}else{
//同源下,即可访问name
console.log(frame.contentWindow.name)
}
}
</script>
</body>
复制代码
http-proxy
当网络请求跨域时,可以使用代理。
服务器访问服务器没有跨域问题.所以,我们的做法是利用中间的代理浏览器向目标浏览器发请求。
- 分别开启3000端口和4000端口的服务
- a页面放在3000端口中
- a页面发送请求,3000端口的服务器转发这个请求到4000端口
a.js
const express = require('express');
const { createProxyMiddleware } = require('http-proxy-middleware');
//将请求转发到另一个服务器
const apiProxy = createProxyMiddleware('/say', {
target: 'http://localhost:4000',
});
const app = express();
app.use(express.static(__dirname));
app.use(apiProxy);
app.listen(3000, function () {
console.log('3000启动');
});
复制代码
b.js
const express = require('express');
const app = express();
app.use(express.static(__dirname));
app.get('/say', function (req, res) {
res.end('我是b服务器的信息');
});
app.listen(4000, function () {
console.log('4000启动');
});
复制代码
<script>
let xhr = new XMLHttpRequest();
xhr.open('get', 'http://localhost:3000/say');
xhr.onreadystatechange = function () {
if (xhr.readyState === 4 && 300 >= xhr.status && xhr.status >= 200) {
console.log(xhr.response);
}
};
xhr.send();
</script>
复制代码
http-proxy-middleware的更多参数请看https://github.com/chimurai/http-proxy-middleware
document.domain跨域
举个例子www.video.baidu.com和www.map.baidu.com是两个二级域名,
// www.map.baidu.com
<body>
<script>
window.name='huzhiwu'
</script>
</body>
// www.video.baidu.com
<body>
<iframe src='www.map.baidu.com' id='frame' onload='load'>
</iframe>
<script>
function load(){
let frame = document.getElementById('frame')
console.log(frame.contentWindow.name)//跨域访问不到
}
</script>
</body>
复制代码
因为跨域访问不到,
但只要在两个二级域名中加个document.domain='baidu.com'
即可互相访问
代码
结语
作者:胡志武
时间:2020/05/06
如果喜欢的话,请点个赞吧,如果有错漏处,请指正
来源:oschina
链接:https://my.oschina.net/u/4408053/blog/4269177