一、什么是跨域?
在了解什么是跨域的时候,我们首先要了解一个概念,叫同源策略,什么是同源策略呢,就是我们的浏览器出于安全考虑,只允许与本域下的接口交互。不同源的客户端脚本在没有明确授权的情况下,不能读写对方的资源。
是什么意思呢,就比如你刚刚登录了淘宝买了东西,但是你现在又点进去了另外一个网站,那么你现在的淘宝账户是属于登录状态,而并没有登出,所以你现在点进去的这个网站可以看到你的账户信息,并操作你的账户信息,这样子就很危险。
我们在来了解一个概念,就是本域,什么是本域呢?就是同协议,同主机,同端口就叫本域。 常见的跨域场景
http://www/a.com/b.js 同一域名。允许通信
https://www/a.com/b.js 协议不同,跨域
script.a.com/a.js 主域相同,子域不同,跨域
那什么时候跨域呢,如下浏览器请求的三种报错:
1、请求未发送
2、请求发送后,服务器发现不一样,服务器未反应。
3、请求发送,服务器有反应,数据返回的时候,浏览器发现不对,被拦截。
同源策略限制了ajax请求,但有三个标签没有被同源策略所影响
<img src=""><link href=""><script src="">
特别说明
- 如果是端口和协议造成的跨域,前端是没有办法解决的
- 跨域仅仅是根据URL的首部来识别,不会根据这个首部对应的ip地址来判断
- 跨域并不是请求没有发出去,请求是能发出去,服务期也能响应,只是响应结果被浏览器拦截了
二、跨域的解决方案
1. jsonp
原理: 利用 script 标签中的 src 属性不会被同源策略所拦截的这一机制,将我们要请求的URL 地址,添加到script的src属性中,并且携带上前端的一个函数名作为参数给到后端,后端获取到前端传递的函数名,返回该函数的调用语法,将要返回的数据放在该函数中作为参数,当浏览器接收到全局下的函数被调用的后端响应时,会自动执行该函数,从而从参数中获取到后端返回的数据
//前端
<!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">
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.5.0/jquery.min.js"></script>
<title>Document</title>
</head>
<body>
<button id="btn">发请求</button>
<script>
let btn = document.getElementById('btn')
btn.addEventListener('click', () => {
// $.ajax({
// url: 'http://localhost:3000/home',
// method: 'get',
// scuccess(res) {
// console.log(res);
// }
// })
// jsonp
function jsonp({ url, callback }) {
return new Promise((resolve, reject) => {
let script = document.createElement('script')
window[callback] = function (data) { // 还没执行
resolve(data) // show函数的参数
document.body.removeChild(script)
}
script.src = `${url}?callback=${callback}` // <script src="http://localhost:3000/home?callback='show'" />
document.body.appendChild(script) // script标签只要出现在了body里,浏览器就会自动加载其src中的资源
})
}
jsonp({
url: 'http://localhost:3000/home',
callback: 'show'
}) // 后端会返回一个 show(),导致了window上的show函数执行了
.then(data => {
console.log(data);
// 拿到了数据之后的操作...
})
})
</script>
</body>
</html>
//后端
const express=require('express')
const app=express()
app.get('/home',(req,res)=>{
// console.log(req.query); ///show
let {callback}=req.query
res.end(`${callback}('hello word')`) //'show()'
})
app.listen(3000,()=>{
console.log('3000端口已启动');
})
缺点:
-
需要对方的服务期做支持才可以
-
只支持 get 请求,有局限性,可能会遇到xss攻击
2. cors
cors是W3C的标准,它允许浏览器向跨源服务器,发出 XMLHttpRequest 请求。也就是说浏览器发请求是不会被跨域的,跨域的核心是后端响应不了。
要让后端响应内容能够不被浏览器拦截,关键在于后端。如果后端也能遵从 cors标准的话,后端的响应也可以跨源
-
简单请求 · 使用get、post、head
· Content-Type的值仅限于 text/plain || multipart/form-data || appplication/x-www-form-urlencoded
-
复杂请求 · 不满足简单请求的条件的就是复杂请求 · 复杂请求的cors请求,会在正式通信之前,增加一次http查询请求,称为“预检”,预检是用来知道服务端是否允许跨域请求,预检请求发的是options方法
<!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">
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.5.0/jquery.min.js"></script>
<title>简单请求</title>
</head>
<body>
<button id="btn">发请求</button>
<script>
let btn = document.getElementById('btn')
btn.addEventListener('click', () => {
$.ajax({
url: 'http://localhost:3000/home',
method: 'get',
headers: {
'Content-Type': "text/plain"
},
scuccess(res) {
console.log(res);
}
})
})
</script>
</body>
</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>复杂请求</title>
</head>
<body>
<button id="btn">发请求</button>
<script>
let btn = document.getElementById('btn')
btn.addEventListener('click', () => {
let xhr = new XMLHttpRequest()
document.cookie = 'name=wn'
xhr.withCredentials = true //xhr这个请求必将去浏览器的cookie里找cookie并携带上
xhr.open('PUT', 'http://localhost:3000/getData', true)
xhr.setRequestHeader('name', 'wn')
xhr.onreadystatechange = function() {
if (xhr.readyState === 4 && xhr.status === 200) {
console.log(xhr.response);
}
}
xhr.send() // 请求发送
})
</script>
</body>
</html>
后端
//app.js
const express = require('express')
const app = express()
let whitList = ['http://127.0.0.1:5500'] // 设置白名单
const cors = (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')
// 允许哪个方法访问我
res.setHeader('Access-Control-Allow-Methods', 'PUT'),
// 允许携带cookie
res.setHeader('Access-Control-Allow-Credentials', true)
// 预检的存活时间
res.setHeader('Access-Control-Max-Age', 6)
// 允许发回的头
res.setHeader('Access-Control-Expose-Headers', 'name')
if (req.method === 'OPTIONS') {
res.end() // 预检请求不做任何处理
}
}
// console.log(origin);
next()
}
app.use(cors)
app.get('/home', (req, res) => { // 简单请求
console.log(req.headers);
res.end('hello world')
})
app.put('/getData', (req, res) => { // 复杂请求
console.log(req.headers);
res.end('hello world 复杂请求')
})
app.listen(3000, () => {
console.log('3000端口已启动');
})
//app2.js
const express = require('express')
const app = express()
const cors = require('cors')
app.use(cors())
app.get('/home', (req, res) => { // 简单请求
// console.log(req.headers);
res.end('hello world')
})
app.put('/getData', (req, res) => { // 复杂请求
// console.log(req.headers);
res.end('hello world 复杂请求')
})
app.listen(3000, () => {
console.log('3000端口已启动');
})
3. postMessage
html5 中的 xhr 提供的API,postMessage()方法允许来自不同源的脚本采用异步的方式进行有限通信,可以实现跨文本,多窗口,跨域消息传递
可以解决这么几个问题:
- 页面和其他新的窗口的数据传递
- 多窗口之间的消息传递
- 页面与嵌套的iframe消息传递
- 上面三个场景的跨域数据传递
otherWindow.postMessage(message, targetOrigin, [transfer]) - message: 要发送给其他window的数据 - targrtOrigin:目标窗口 - transfer(可选)和message一起传递的一个对象,这个对象的所有权将移交给消息接收方
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 页面 -- 哆啦A梦的大朋友
<iframe src="http://127.0.0.1:5500/postMessage/b.html" frameborder="0" id="frame" onload="load()"></iframe>
<script>
function load() {
let frame = document.getElementById('frame')
frame.contentWindow.postMessage('海绵宝宝的朋友是派大星', 'http://127.0.0.1:5500') // 给b页面传数据
window.onmessage = function(e) { // 接收b页面返回的数据
console.log(e.data);
}
}
</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>
page B --- 海绵宝宝
<script>
window.onmessage = function(e) {
console.log(e.data);
e.source.postMessage('哆啦A梦的朋友是大雄', e.origin)
}
</script>
</body>
</html>