1.了解什么是跨源?
前端调用的后端接口不属于同一个域,这就是跨域。简单来理解就是协议、域名和端口有任意一个不一样的情况下请求数据,就会被认为是跨域。跨域请求如果没被特殊处理的话,就会报错。
服务器端代码(NodeJS)
import express from 'express';
const app = express();
let port = 7000;
let ip = '127.0.0.1'
let userObj = {
name :'hzh',
age : 29,
sex : '男'
}
app.get('/user',(req,res) => {
// res.setHeader('Access-Control-Allow-Origin','*');
res.send(userObj);
})
app.listen(port,() => {
console.log(`your sever is running at http://${ip}:${port}`)
})
客户端代码
document.querySelector('button').addEventListener('click',() =>{
let xhr = new XMLHttpRequest();
xhr.onload = function(){
if((xhr.status === 200 || xhr.status < 300) || xhr.status === 304){
console.log(xhr.responseText);
}else{
console.log('requset error');
}
}
xhr.open('GET','http://127.0.0.1:7000/user',true);
xhr.send(null);
})
会出现以下错误,因为没有对请求进行特殊处理,则请求会被跨源政策(同源策略)所拦截并爆出错误
2.什么是同源策略?
同源策略(协议、域名、端口号相同就称为同源)是浏览器的安全策略,它用于限制一个origin的文档或者它加载的脚本如何能与另一个源的资源进行交互。同源策略的好处在于,它能帮助阻隔恶意文档,减少可能被攻击的媒介,增强浏览器数据传输的安全性。所以跨域请求之所以会报错,全部来源于浏览器的同源策略。
2.1浏览器的同源策略到底限制了什么?
其实浏览器并不是限制了所有的跨源操作,因为我们在通过浏览器去测试跨域时,我们虽然收到了跨源报错,但是请求状态status依旧显示200,也就是能正常返回,只是我们读取不到返回的数据而已,也就是说浏览器的同源策略限制了在非同源情况下的读数据操作。
3.IE特殊的同源策略是什么?
Internet Explorer 的同源策略有两个主要的差异点:
-
授信范围(Trust Zones):两个相互之间高度互信的域名,如公司域名(corporate domains),则不受同源策略限制。
-
端口:IE 未将端口号纳入到同源策略的检查中,也就是说即使两个域的端口号不一样,协议和域名一样,则也能互相建立连接不会报错。
4.跨源资源共享解决跨域问题
虽然浏览器有同源策略,不支持跨域数据请求,但是浏览器也是可以支持合法的跨源访问的能力,以XHR对象为案例,XHR对象受浏览器的影响只能访问与发起请求的页面在同一个域内的资源。跨源资源共享CORS(Cross-Origin Resource Sharing)定义了浏览器与服务器如何实现跨源通信,也就是说要不要跨源传输资源,要么是刘浏览器通过某种方式告诉服务器可以,要么是服务器通过某种方式告诉浏览器可以。CORS背后的基本逻辑是使用自定义的HTTP头部(这个自定义头部可以是请求上传的也可以是服务器响应的)允许浏览器和服务器进行相互了解,以确实请求的响应是成功还是失败。
注意:
跨域XHR对象可以访问status和statusText属性的值,也允许同步请求,但是出于安全考虑,跨域XHR对象有一些额外的限制
-
不能使用setRequsetHeader()设置自定义请求头
-
不能发送或接收cookie
-
getAllResponseHeader()方法始终返回空字符串
4.1几种请求方式
在HTTP1.1 协议中的,请求方法分为GET、POST、PUT、DELETE、HEAD、TRACE、OPTIONS、CONNECT 八种。浏览器根据这些请求方法和请求类型将请求划分为简单请求和非简单请求。
4.2简单请求处理跨源资源共享问题
在以上八种请求中,我们把POST、GET请求理解为简单的请求,简单请求是没有自定义头部的,则想要达到跨域传输资源,则必须通过服务器告诉浏览器可以跨源传输资源,那样才可以达成协议,沟通好使用CORS。服务器发送Access-Control-Allow-Origin头部,值为"*",这样服务器就是在告诉浏览器可以进行跨源请求资源
res.setHeader('Access-Control-Allow-Origin','*'); // 通配符*表名该访问的数据是被共享的
4.3非简单请求处理跨域资源共享问题——预检请求
以上八种请求除了POST、GET请求之外都是非简单请求,也就是允许使用自定义请求头,说白了就是可以通过浏览器来告诉服务器,我们允许跨源请求资源。CORS通过一种叫预检请求(preflighted request)的服务器验证机制,在同源策略关闭的情况下(服务器书写了Access-Control-Allow-Origin等属性),之后再进行敏感操作之前,这个时候浏览器会向同一个URL发出OPTIONS类型的请求,这就是预检请求
CORS规范要求
对那些可能对服务器数据产生副作用的 HTTP 请求方法(特别是 GET 以外的 HTTP 请求,或者搭配某些 MIME 类型的 POST 请求),浏览器必须首先使用 OPTIONS 方法发起一个预检请求(preflight request),从而获知服务端是否允许该跨域请求。
满足下面其中一个条件浏览器就会自行触发OPTIONS请求(预检请求)
-
使用了PUT/DELETE/CONNECT/OPTIONS/TRACE/PATCH任意一个方法
-
人为设置了除Accept/Accept-Language/Content-Language/Content-Type/DPR/Downlink/Save-Data/Viewport-Width/Width字段的其他字段
-
Content-Type 的值不属于application/x-www-form-urlencoded、multipart/form-data、text/plain其中任何一个
这个请求使用OPTIONS方法发送并包含以下头部:
-
Origin: <origin>
-
Access-Control-Request-Method: 请求希望使用的方法
-
Access-Control-Request-Headers: (可选)要使用的逗号分隔的自定义头部列表
服务器会进行响应发送过来的请求头包含如下:
-
Access-Control-Allow-Origin: <origin> | *
-
Access-Control-Allow-Methods: 允许的方法(多个方法利用逗号分隔)
-
Access-Control-Allow-Headers: 服务器允许的头部(逗号分隔的列表)
-
Access-Control-Max-age: 缓存预检请求的秒数(优化预检请求的方法就是缓存它)
新版本谷歌浏览器怎么看预检请求
注意:(预检请求常常出现的基本错误)
因为浏览器自行进行了预检请求,则服务器端必须对该请求做出响应,如果不对该请求响应,做跨源处理,则浏览器依旧会报跨源错误,这个时候,我们只需要再服务器端先放上Access-Control-Allow-Origin字段,之后给上值,随后放上Access-Control-Allow-Methods字段,并且给上OPTIONS请求类型和其他的你想让其通过跨源请求的请求类型,案例如下,我想发送DELETE请求,并想浏览器认同跨源请求且认同DELETE请求:
服务器端代码(NodeJS)
import express from 'express';
const app = express();
let port = '8080';
let ip = '127.0.0.1'
let userObj = {
name :'hzh',
age : 29,
sex : '男'
}
// 服务器端接收预检请求,OPTIONS请求
app.options('/user',(req,res) => {
res.setHeader('Access-Control-Allow-Origin','*');
res.setHeader('Access-Control-Allow-Methods','OPTIONS,DELETE'); // 这里的请求方法必须含有DELETE方法,不然依旧是不通过DELETE请求方法
console.log('通过了预检请求')
res.send('通过预检请求');
})
app.delete('/user',(req,res) => { // 服务器端给DELETE请求进行响应
res.setHeader('Access-Control-Allow-Origin','*');
console.log('通过delete请求');
res.send('发送了delete请求');
})
app.listen(port,() => {
console.log(`your sever is running at http://${ip}:${port}`)
})
客户端代码(XHR对象发送请求)
document.querySelector('button').addEventListener('click',() =>{
let xhr = new XMLHttpRequest();
xhr.onload = function(){
console.log(xhr.status,xhr.statusText)
if((xhr.status === 200 || xhr.status < 300) || xhr.status === 304){
console.log(xhr.responseText);
}else{
console.log('requset error');
}
}
xhr.open('DELETE','http://localhost:8080/user',true); // 发送DELETE请求
xhr.send(null);
})
5.跨源请求是否能携带Cookie?
默认情况下,跨源请求是不允许携带Cookie的,且还不允许携带HTTP认证以及客户端SSL证书,但是我们可以通过将withCredemtials属性值设置为true来表明请求会携带Cookie、HTTP认证以及客户端SSL证书,如果服务器允许携带Cookie、HTTP认证以及客户端SSL证书的请求,那么可以响应中包含如下HTTP头部:
Access-Control-Allow-Credentials: true;
注意:
-
如果你发送了一个携带Cookie、HTTP认证以及客户端SSL证书的请求,而服务器没有响应以上请求头,则通过JS就获得不到这个响应(responseText是空字符、status是0、onerror()被调用)
-
服务器也可以通过预检请求的响应中发送这个HTTP请求头,以表明这个源被允许发送携带Cookie、HTTP认证以及客户端SSL证书的请求