前端跨域大全
在这篇文章中,你会了解到
- 浏览器为什么要跨域
- 常见的几种跨域方法以及其对应的优势和弊端
- 几种常见跨域方式的代码实现
一、什么是跨域以及为什么要限制跨域
(1)什么是跨域
- 浏览器同域策略:协议、域名、 端口,都一样
http://store.company.com/dir2/other.html 同源 只有路径不同
http://store.company.com/dir/inner/another.html 同源 只有路径不同
https://store.company.com/secure.html 失败 协议不同
http://store.company.com:81/dir/etc.html 失败 端口不同 ( http:// 默认端口是80)
http://news.company.com/dir/other.html 失败 主机不同
因此只要有一种不一样,即算作跨域请求。
(2)为什么要限制跨域这里我就真的不罗嗦了,大家看下面那篇文章即可,已经讲的很好啦
目前,如果非同源,共有三种行为受到限制。
- 无法获取非同源网页的 cookie、localstorage 和 indexedDB。
- 无法访问非同源网页的 DOM (iframe)。
- 无法向非同源地址发送 AJAX 请求 或 fetch 请求(可以发送,但浏览器拒绝接受响应)。
对于三种行为的危害更生动的描述,可以看这篇文章
不要再问我跨域的问题了
二、有哪些常见的跨域方式?
jsonp
cors
postMessage
document.domain
window.name
location.hash
图像Ping
http-proxy(例如webpack反向代理)
nginx
Comet(服务器推送技术)
websocket
1.jsonp
(1)原理:
- 利用
<script>
标签的 src 属性没有跨域的限制 - 访问的地址返回一个预先定义好的 Javascript 函数的调用
- 将服务器数据以该函数参数的形式传递给前端
(2)jsonp代码实现
function jsonp({url,params,callback}){
return new Promise((resolve,reject)=>{
let script = document.createElement('script')
window[callback] = function(data){
resolve(data)
document.removeChild(script)
}
script.src = `${url}?${encodeParams(params)}`
document.body.appendChild(script)
})
}
function encodeParams(obj){
const params = []
Object.keys(obj).forEach(key=>{
let value = obj[key]
if(typeof value === 'undefined'){
value = ''
}
params.push(`${key}=${value}`)
})
return params.join('&')
//例如 当obj = {name:"老王",id:"1"}
//返回 name=老王&id=1
}
jsonp({
url:"http://localhost/student", //真实情况下请求该链接返回的是一个可以返回数据的可执行函数
params:{name:"小明",id:"1",action:"奥里给"}, //需要的参数
callback:'show' //对应链接中返回的函数名
}).then(data=>{
//打印出请求该url所得到得数据
console.log(data)
})
服务器端代码
let express = require('express')
let app = express()
app.get('/student',function(req,res){
let {name,id,action,callback} = req.query
console.log(name,id,aciton) // 小明,1,奥里给
res.end(`${callback}('传入要返回的数据')`)
})
app.listen(3000)
(3)缺陷
- 只能使用GET请求
- 存在遭受XSS(跨域脚本攻击的风险)
2.CORS跨域资源共享
(1)原理
CORS(Cross-Origin Resource Sharing)是W3C的一个工作草案,定义了在必须访问跨域资源时,浏览器与服务器应该如何沟通。CORS背后的思想,就是使用自定义的HTTP头部让浏览器与服务器进行沟通,从而决定请求或响应是应该成功还是失败。
(2)代码实现
首先我们运行两个简单的本地服务器
server1
let express = require('express')
let app = express()
app.use(express.static(__dirname)) //可访问静态资源
app.listen(3000)
server2
let express = require('express')
let app = express()
app.get('/getData',function(res,res){
res.end("你被黑人兄弟抬走了")
})
app.listen(4000)
然后就让咱们一个一个撸过去吧!CORS实际上不常用,咳咳,但还是有掌握的必要的
- Access-Control-Allow-Origin
...其它部分省略
<script>
let xhr = new XMLHttpRequest;
xhr.open('GET','http://localhost:4000/getData',true)
xhr.onreadystatechange = function(){
if(xhr.readyState === 4){
if(xhr.status >= 200 && xhr.status === 304
|| xhr.status === 300){
console.log(xhr.response)
}
}
}
xhr.send()
</script>
...
新建一个hmtl页面,建立XHR请求,去访问4000端后的’/getData’,由于该html文件是运行在3000端口的,此时就形成了跨域。
...省略
let whilList = ['http://localhost:3000'] //设置白名单
app.use(function(req,res,next){
let origin = req.headers.origin
if(whilList.includes(origin)){
//设置哪个源可以访问
res.setHeader('Access-Control-Allow-Origin',origin) //设置可访问的源
}
next()
})
然后在浏览器界面就可以看到你被黑人兄弟抬走了
- Access-Control-Allow-Headers
let xhr = new XMLHttpRequest;
xhr.open('GET','http://localhost:4000/getData',true)
xhr.setRequestHeader('name','黑人抬棺') //设置访问的请求头
xhr.onreadystatechange = function(){
if(xhr.readyState === 4){
if(xhr.status >= 200 && xhr.status === 304
|| xhr.status === 300){
console.log(xhr.response)
}
}![在这里插入图片描述](https://img-blog.csdnimg.cn/20200601071345672.png#pic_center)
}
xhr.send()
如果你请求头改变的话,此时浏览器会报出这种错误
let whilList = ['http://localhost:3000']
app.use(function(req,res,next){
let origin = req.headers.origin
if(whilList.includes(origin)){
//设置哪个源可以访问
res.setHeader('Access-Control-Allow-Origin',origin)
res.setHeader('Access-Control-Allow-Headers','name,x,x,x,') //设置多个合法的头部信息
}
next()
})
然后在浏览器界面就可以看到你被黑人兄弟抬走了
- Access-Control-Allow-Methods
从此处开始就只贴出针对上一个知识点修改过的代码了
xhr.open('PUT','http://localhost:4000/getData',true)
改变了请求的方式,此时又会看到这样的错误
res.setHeader('Access-Control-Allow-Methods','PUT')
然后在浏览器界面就可以看到你被黑人兄弟抬走了
- Access-Control-Allow-Credentials
document.cookie = 'name=黑人抬棺'
xhr.widthCredentials = true
当请求携带cookie的时候,又会发现出错
res.setHeader('Access-Control-Allow-Credentials',true)
此时就能成功接收到cookie了
3.postMessage
postMessage是两个页面之间的通信
a.html
<body>
<iframe src="http://localhost:4000/b.html"
frameborder="0" id="frame" onload="load()"></iframe>
</body>
<script>
function load(){
let frame = document.getElementById('iframe')
frame.contentWindow.postMessage('你要被黑人兄弟抬走了')
window.onmessage = function(e){
console.log(e.data) //好的,我已经躺进去了
}
}
</script>
b.html
window.onmessage = function(e){
console.log(e.data) //'你要被黑人兄弟抬走了'
e.source.postMessage('好的,我已经躺进去了')
}
此时就可以成功的在浏览器看到你要被黑人兄弟抬走了
,好的,我已经躺进去了
4.window.name + iframe
前端常见跨域解决方案(全)
5.设置nginx代理
代码实例
if ($request_method = 'OPTIONS') {
return 204;
}
add_header Access-Control-Allow-Origin * always;
add_header Access-Control-Allow-Headers "Content-Type, Authorization" always;
add_header Access-Control-Allow-Methods "GET, POST, OPTIONS, PUT, PATCH, DELETE, HEAD" always;
add_header Access-Control-Max-Age 86400 always;
解析:
1.首先判断请求方法是否为OPTIONS,OPTIONS请求方法为预检请求,当浏览器需要跨域请求的时候,会先使用该方法请求服务器,服务器返回正确的信息之后,浏览器才会发送正式请求,(一般除了 GET 方法外的跨域请求都会先发送预检请求),预检请求的返回状态码为 204 代表成功,其它均为失败。 预检请求没有返回的 Body。
2.Access-Control-Allow-Origin,代表允许域名下的页面来请求这个地址,一般不会设置为*号,而是
add_header Access-Control-Allow-Origin www.example.com
3.Access-Control-Allow-Origin,代表允许在请求该地址的时候带上指定的请求头,例如Content-Type,Authorization,使用,号分割,放在双引号中。
add_header Access-Control-Allow-Headers "Content-Type, Authorization" always;
4.Access-Control-Allow-Methods,代表允许使用指定的方法请求该地址
add_header Access-Control-Allow-Methods "GET, POST, OPTIONS, PUT, PATCH, DELETE, HEAD" always;
5.Access-Control-Max-Age,代表着在限定的时间(比如上述的86400)秒之内,再次请求该地址的时候,不需要进行预检请求,也就是跨域缓存。
add_header Access-Control-Max-Age 86400 always;
上述的add_header最后都加上了always,它标识不管返回状态码是多少,都会使得add_header生效。
参考资料:
彻底理解浏览器的跨域
前端常见跨域解决方案(全)
不要再问我跨域的问题了
详解跨域(最全的解决方案)
什么是跨域?跨域解决方法
感谢阅读,欢迎批评指正,希望大家能够在追求卓越中不断进步,让优秀成为一种习惯~