一、 同源、非同源/跨域
仅协议、ip、端口完全相同的地址才算同源请求,三者任意一个不一样为非同源请求/跨域请求。
二、跨域行为的限制
-
无法读取到非同源的iframe内容
-
无法读取到非同源cookie
-
无法获取非同源ajax请求数据
三、Ajax跨域解决
浏览器抛出了跨域请求错误,并不是代表跨域请求没有发出去,而是发出去了并且响应成功,但是浏览器会在把数据交给js脚本之前会做校验比对。如果校验通过,则继续把响应数据交给Js脚本,否则会抛出跨域异常。
- 浏览器的校验策略是什么?
如果是跨域请求,在响应返回时需要在响应头中设置如下响应头字段,值是什么?值就是请求头中的Origin,Origin请求头表示发起请求的源。
Access-Control-Allow-Origin :xxx
这里请求头字段中的Origin 仅会包含协议、域名、端口,主要作用就是用于帮助服务器识别来源域。
如果服务器允许这个源访问,则需要在响应头中设置 Access-Control-Allow-Origin :http://127.0.0.1:5500,注意,这里的格式必须完全保持和请求头中的Origin一致。
- 代码示例
前端示例代码:
function getAjaxData(){
$.ajax({
url:"http://127.0.0.1:3000/list",
type:"get",
success(res){
console.log("ajaxData",res);
}
})
}
Express服务修改:
app.get('/list',(res,req) => {
req.header('Access-Control-Allow-Origin','http://127.0.0.1:5500')
req.send(listData)
})
当响应头添加允许跨域标识字段之后,此时校验规则通过,Js脚本可以获取数据。
- 预检请求发送
当发送ajax请求时自定义了header,会发现此时又跨域报错了
而且发现浏览器多发了一次请求,这就是预检请求。
预检请求什么时候发出?
- 当跨域请求的请求方法非 GET PUT DELETE时。
- 当自定义了Http Header时。
- 当请求内容类型非 application/x-www-form-urlencoded、multipart/form-data、text/plain时。
验证预检请求:
function getAjaxData(){
$.ajax({
url:"http://127.0.0.1:3000/list",
type:"post",
headers:{
SaToken:"token"
},
contentType: 'application/json', // 告诉服务器发送的数据类型
dataType: 'json',
data:JSON.stringify({id:1}),
success(res){
console.log("ajaxData",res);
}
})
}
这里我自定义了header且修改了请求content-type字段,当修改了这个字段时,浏览器发现不是默认允许的三种内容类型,就在预检请求中添加了content-type头。
此时服务器需要继续设置新的响应头来告诉浏览器允许跨域。
Express代码修改:
app.options('/list',(res,req) =>{
req.header('Access-Control-Allow-Headers','content-type,satoken')
req.header('Access-Control-Allow-Origin','http://127.0.0.1:5500')
req.send("OK");
})
app.post('/list',(res,req) => {
req.header('Access-Control-Allow-Origin','http://127.0.0.1:5500')
req.send(listData)
})
此时可以看到预检请求的响应头中包含了请求头所需要的校验header。
继续验证PUT请求方法
function getAjaxData(){
$.ajax({
url:"http://127.0.0.1:3000/list",
type:"put",
dataType: 'json',
data:JSON.stringify({id:1}),
success(res){
console.log("ajaxData",res);
}
})
}
注意,此时浏览器报错提示的是Access-Control-Allow-Methods头缺失。
Express服务代码修改:
app.options('/list',(res,req) =>{
req.header('Access-Control-Allow-Headers','content-type,satoken')
req.header('Access-Control-Allow-Origin','http://127.0.0.1:5500')
req.header('Access-Control-Allow-Methods','*')
req.send("OK");
})
设置了Access-Control-Allow-Methods响应头之后可以保证预检请求通过。
这里需要注意的是,在预检请求的响应头设置了相关跨域头之后,在实际请求的响应头上也需要设置相关允许跨域字段。
- 跨域请求响应头暴露
后端在自定义设置响应头时,如果是跨域请求,且没有设置相关暴露响应头字段,则前端无法获取响应头数据
app.put('/list',(res,req) => {
req.header('Access-Control-Allow-Origin','http://127.0.0.1:5500')
req.header('myHeader',"hello world")
req.send(listData)
})
function getAjaxData(){
$.ajax({
url:"http://127.0.0.1:3000/list",
type:"put",
dataType: 'json',
data:JSON.stringify({id:1}),
success(res, textStatus, jqXHR){
console.log("ajaxData",res);
console.log('Response Headers:', jqXHR.getAllResponseHeaders());
}
})
}
设置Express请求允许暴露跨域请求获取响应头
app.put('/list',(res,req) => {
req.header('Access-Control-Allow-Origin','http://127.0.0.1:5500')
req.header('Access-Control-Expose-Headers', '*');
req.header('myHeader',"hello world")
req.send(listData)
})
此时控制台可以打印出所有的跨域请求设置的响应头。
四、跨域请求其他解决方案
跨域方案 | 说明 |
---|---|
JsonP | 只能支持Get请求,依赖Script标签,局限性大 |
devServer | 脚手架开发环境配置devServerProxy代理,优雅解决 |
五、跨域请求Cookie携带问题
-
跨域未跨站请求,服务器设置cookie的domain为.xxx.com顶级域名,且Cookie属性为Secure。
当发送未跨站请求时,会自动携带cookie。 -
跨域且跨站,响应端设置Cookie时,需要为Secure属性,SameSite=None,且前端需要设置withCredentials ,后端响应头设置允许跨域请求携带Cookie。