前后端交互 -- ajax跨域之CORS
解决跨域--CORS一. CORS跨域设置1. 允许跨域 Access-Control-Allow-Origin2. 允许客户端获取的头部信息 Access-Control-Expose-Headers3. 允许前端设置的头部 Access-Control-Allow-Headers4. 设置前端允许发送的请求方式 Access-Control-Allow-Methods5. 允许携带凭证 Acc
解决跨域--CORS
几种常用跨域方式区别:
jsonp : 前端来完成
CORS: 前后端配合完成 (存在预检请求的情况)
后端代理: 后端完成
一. CORS跨域设置
概念
CORS(Cross-origin resource sharing),跨域资源共享,是⼀份浏览器技术的规范,⽤来避开
浏览器的同源策略
简单来说就是解决跨域问题的除了jsonp外的另⼀种⽅法;⽐jsonp更加优雅。
CORS是一个W3C标准,全称是"跨域资源共享"(Cross-origin resource sharing),它允许浏览器向跨源服务器,发出XMLHttpRequest请求,从而克服了AJAX只能同源使用的限制,以避开浏览器的同源策略,是 JSONP 模式的现代版。
与 JSONP 不同,CORS 除了 GET 要求方法外,也支持其他的 HTTP 请求。用 CORS 可以让网页设计师用一般的 XMLHttpRequest,这种方式的错误处理比 JSONP 好。另一方面,JSONP可以在不支持 CORS 的老旧浏览器上运作,现代浏览器都支持 CORS。
1. 允许跨域 Access-Control-Allow-Origin
("Access-Control-Allow-Origin","*")
('Access-Control-Allow-Origin', 'http://www.baidu.com')
ctx.set("Access-Control-Allow-Origin","*");
(1)这个表示任意域名都可以访问,不安全
(2)不能携带凭证,默认不能携带cookie了。(必须字段)ctx.set("Access-Control-Allow-Origin","http://localhost:3000");
这样写,只有http://localhost:3000可以访问
2. 允许客户端获取的头部信息 Access-Control-Expose-Headers
(–响应头部–)
("Access-Control-Expose-Headers",'Content-Type, Content-Length, Authorization, Accept, X-Requested-With , yourHeaderFeild');
后端非同源接口代码:
ctx.set("Access-Control-Expose-Headers",'Content-Type,Content-Length,Date');
(1) 允许客户端获取的头部key;
(2) 用set或者header都可以;
(3) CORS请求时,XMLHttpRequest 对象的 getResponseHeader() ⽅法只能拿到6个基本字段:Cache->Control 、 Content-Language 、 Content-Type 、 Expires 、 Last-Modified 、 Pragma。
如果想拿到其他字段,就必须在 Access-Control-Expose-Headers ⾥⾯指定。
对应前端的 获取返回头部信息:xhr.onload = function(){ console.log(xhr.responseText); // 获取返回头部信息 let res = xhr.getAllResponseHeaders(); console.log(res);// date: Mon, 13 Apr 2020 11:43:32 GMT // content-type: text/plain; charset=utf-8 // content-length: 21 }
3. 允许前端设置的头部 Access-Control-Allow-Headers
(–请求头部–)
设置允许requset设置的头部
使用cors方式来跨域时,跨域时会限制设置请求头部,只能去找后端 —> 允许前端设置的头部(请求头部)
('Access-Control-Allow-Headers','Content-Type,Content-Length,Authorization, Accept,X-Requested-With,yourHeaderFeild');
后端非同源接口代码:
ctx.set("Access-Control-Allow-Headers","Content-Type,Content-Length,Authorization,test")
对应前端的 设置头部信息
xhr.setRequestHeader("Content-Type","application/x-www-form-urlencoded"); xhr.setRequestHeader("Content-Type","application/json"); xhr.setRequestHeader("test","some value...");//属性test随便写的。。注意服务器与浏览器要一致
4. 设置前端允许发送的请求方式 Access-Control-Allow-Methods
ctx.set("Access-Control-Allow-Methods",'GET,POST,DELETE,HADE,OPTIONS');
5. 允许携带凭证 Access-Control-Allow-Credentials
ctx.set("Access-Control-Allow-Credentials",true)
凭证 例如post参数,cookie信息等等(非同源获取不到,所以需要在前端开启一下,后端也允许一下该凭证)
后端非同源接口代码:ctx.set("Access-Control-Allow-Credentials",true);
对应前端的开启 允许携带凭证
xhr.withCredentials = true;
6. 预检请求 & 设置预检请求的缓存时间 Access-Control-Max-Age
6.1 预检请求
6.1.1 概念
1.预检请求 options —> 允许前端设置头部信息
2.预检请求:不符合 可直接发送的简单请求 条件时,会出现预检请求(简单请求的不会出现预检请求)
可直接发送的简单请求:例如:GET,HEAD,POST,或者content-type:text/plain,multipart/form-data,application/x-www-form-urlencoded
6.1.2 出现的预检请求的情况
(1)指定的请求类型时 (
options
)预检请求指定类型 例如:PUT,DELETE,CONNECT,OPTIONS,TRACE,PATCH
(2)修改了指定的属性 (自定义请求头
)
(3)修改指定的属性值 (例如:请求头中的content-type是application/x-www-form-urlencoded,multipart/form-data,text/plain之外的格式
)
代码示例:xhr.setRequestHeader("test","some value...");//(2)修改了指定的属性 xhr.setRequestHeader("Content-Type","application/json");//(3)修改指定的属性值
必须是下面定义对CORS安全的首部字段集合,不能是集合之外的其他首部字段。
Accept、Accept-Language、Content-Language、Content-Type、DPR、Downlink、Save-Data、Viewport-Width、Width。Content-Type的值必须是text/plain、multipart/form-data、application/x-www-form-urlencoded中任意一个值
满足上面所有的条件才不会发送预检请求,在实际项目中我们的请求格式可能是application/json格式编码,或者使用自定义请求头都会触发CORS的预检请求。
所以,在项目中是否会触发CORS的预检请求要做到心中有数。
6.1.3 非同源请求情况下的后端代码示例
明确一点,预检请求只出现在跨域请求下哈~~~非跨域请求无需考虑
代码示例://所有的预检请求 options * 路由只做预检请求的允许 router.options("/*",ctx=>{ //1.允许跨域 // ctx.set("Access-Control-Allow-Origin","*"); // * 是通配符 1.不安全,2.不能携带凭证 ctx.set("Access-Control-Allow-Origin","http://localhost:3000"); //2.允许获取的头部信息(响应头部) ctx.set("Access-Control-Expose-Headers",'Content-Type,Content-Length,Date'); //3.设置前端允许发送的 请求 方式 ctx.set("Access-Control-Allow-Methods",'GET,POST,DELETE,HADE,OPTIONS'); //4.允许前端设置的头部(请求头部) ctx.set("Access-Control-Allow-Headers","Content-Type,Content-Length,Authorization,test") //5.允许携带凭证 ctx.set("Access-Control-Allow-Credentials",true); //6.设置预检请求的缓存时间 ctx.set("Access-Control-Max-Age",3600*24); console.log("有预检请求"); ctx.body = ""; });
是因为在某些情况下,普通的get或者post请求会首先自动发起一次options请求,当options请求成功返回后,真正的ajax请求才会再次发起。
1、跨域请求,非跨域请求不会出现options请求
2、自定义请求头
3、请求头中的content-type是application/x-www-form-urlencoded,multipart/form-data,text/plain之外的格式
当满足条件12或者13的时候,简单的ajax请求就会出现options请求,有没有感觉到一点同源策略的意思,个人理>解这个就是浏览器底层对于同源策略的一个具体实现。首先得到服务器端的确认,才能继续下一步的操作,这也>是为什么options请求也被叫做“预检”请求的原因吧。
server端在接收到请求的时候,先去判断下是不是options请求,判断下来源,没问题的时候返回个200之类的成功就可以了。
该段摘来自 关于options请求的一点理解
6.2 设置预检请求的有效期(缓存时间)Access-Control-Max-Age
ctx.set("Access-Control-Max-Age",3600*24);
Access-Control-Max-Age ⽤来指定本次预检请求的有效期,单位为秒,在此期间不⽤发出另⼀条
预检请求。
(1)在没设置有效期时,一次出现两个请求
(2)设置有效期后,第一次获取还是两个,第二次获取就是一个了
代码示例://设置预检请求的缓存时间 ctx.set("Access-Control-Max-Age",3600*24);
二. 代码示例
同源下 服务器代码(3000端口号下):
const Koa = require("koa");
const static = require("koa-static");
const Router = require("koa-router");
const koaBody = require("koa-body");
let app = new Koa();
let router = new Router();
app.use(koaBody());
app.use(static(__dirname+"/static"));//直接使用了静态页面
router.get("/",ctx=>{
ctx.body = "3000端口";
// console.log(ctx.body);
})
router.get("/setcookie",ctx=>{
ctx.cookies.set("username","zhangsan",{
maxAge:1000*60*60*24
});
ctx.body = "设置cookie";
})
router.post("/post",ctx=>{
ctx.body= "3000端口--同源";
// console.log(ctx.body);
});
app.use(router.routes());
app.listen(3000);
前端浏览器页面(3000端口号下):
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<h1>3000端口</h1>
<button class="btn1">请求非跨域/同源</button>
<button class="btn2">请求跨域/非同源</button>
<!-- <form action="" enctype="application/x-www-form-urlencoded"></form> -->
<script>
//使用cors方式来跨域时,跨域时会限制设置请求头部,只能去找后端->4.允许前端设置的头部(请求头部)
//同源情况-------------------------------------------------------
document.querySelector(".btn1").onclick = function(){
let xhr = new XMLHttpRequest();
xhr.open("post","/post",true);
xhr.setRequestHeader("test","some value...");// 同源添加请求头没有任何影响,正常添加就可以
xhr.onload = function(){
console.log(xhr.responseText);
}
xhr.send();
}
//非同源情况-------------------------------------------------------
//使用post请求来传参的话,是需要设置头部的
document.querySelector(".btn2").onclick = function(){
//跨域: 请求成功了
//跨域是浏览器行为,服务器没有跨步跨域,同不同源这一说
let xhr = new XMLHttpRequest();
//地址:
xhr.open("post","http://localhost:4000/post",true);
// xhr.setRequestHeader();
//设置请求头部-------------------------------------------------------
// xhr.setRequestHeader("Content-Type","application/x-www-form-urlencoded");
//虽然是默认的头部,但是加不加还是有区别的
//比如不加的时候 输出结果可能是 name = 张三
//加上的话,输出结果为{ name:'张三'}
// 1.预检请求 options,2.允许前端设置头部信息------------------------
// 2.预检请求:不符合课件中记录的条件时,会出现预检请求(简单请求的不会出现预检请求)
// 出现的预检请求的情况:
// 1.指定的请求类型时(options)
// 2.修改了指定的属性(详情见文档)
// 3.修改指定的属性值(详情见文档)
xhr.setRequestHeader("Content-Type","application/json");
xhr.setRequestHeader("test","some value...");//属性test随便写的。。注意服务器与浏览器要一致
//5.允许跨域请求携带凭证-------------------------------------------------
xhr.withCredentials = true;
xhr.onload = function(){
console.log(xhr.responseText);
//获取返回头部信息--------------------------------------------------------
let res = xhr.getAllResponseHeaders();
console.log(res);//date: Mon, 13 Apr 2020 11:43:32 GMT
// content-type: text/plain; charset=utf-8
// content-length: 21
}
// let data = "name=张三"; //头部为"Content-Type","application/x-www-form-urlencoded"时举例
let data = {
name:"李四" //头部为"Content-Type","application/json"时举例
}
// xhr.send(data);
xhr.send(JSON.stringify(data));
}
</script>
</body>
</html>
非同源下 服务器代码(4000端口号下):
const Koa = require("koa");
const static = require("koa-static");
const Router = require("koa-router");
const koaBody = require("koa-body");
let app = new Koa();
let router = new Router();
app.use(koaBody());
app.use(static(__dirname+"/static"));
router.get("/",ctx=>{
ctx.body = "4000端口"
})
//所有的预检请求 options * 路由只做预检请求的允许
router.options("/*",ctx=>{
//1.允许跨域
// ctx.set("Access-Control-Allow-Origin","*"); // * 是通配符 1.不安全,2.不能携带凭证
ctx.set("Access-Control-Allow-Origin","http://localhost:3000");
//2.允许获取的头部信息(响应头部) (这里用set或者header都可以)
ctx.set("Access-Control-Expose-Headers",'Content-Type,Content-Length,Date');
//3.设置前端允许发送的 请求 方式
ctx.set("Access-Control-Allow-Methods",'GET,POST,DELETE,HADE,OPTIONS');
//4.允许前端设置的头部(请求头部)
ctx.set("Access-Control-Allow-Headers","Content-Type,Content-Length,Authorization,test")
//5.允许携带凭证
ctx.set("Access-Control-Allow-Credentials",true);
//6.设置预检请求的缓存时间
ctx.set("Access-Control-Max-Age",3600*24);
console.log("有预检请求");
ctx.body = "";
});
router.post("/post",ctx=>{
console.log(ctx.request.body);
//1.允许跨域
// ctx.set("Access-Control-Allow-Origin","*"); // 1.不安全,2.不能携带凭证
ctx.set("Access-Control-Allow-Origin","http://localhost:3000");//这样写,只有http://localhost:3000可以访问
//2.允许 获取的头部信息 (--响应头部--) (这里用set或者header都可以) 即允许客户端 获取 的头部key
ctx.set("Access-Control-Expose-Headers",'Content-Type,Content-Length,Date');
//3.设置前端允许发送的请求方式
ctx.set("Access-Control-Allow-Methods",'GET,POST,DELETE,HADE,OPTIONS');
//4.允许前端设置的头部 (--请求头部--)
ctx.set("Access-Control-Allow-Headers","Content-Type,Content-Length,Authorization,test")
//5.允许携带凭证 凭证 例如post参数,cookie信息等等(非同源获取不到,所以需要在前端开启一下,后端也允许一下该凭证)
ctx.set("Access-Control-Allow-Credentials",true);
//6.设置预检请求的缓存时间
ctx.set("Access-Control-Max-Age",3600*24);
// console.log(ctx.query.body);// X
console.log(ctx.request.body);//{ name: '李四' }
ctx.body= "4000端口--非同源";
});
router.post("/Serverpost",ctx=>{
ctx.body = "小点声,我是偷摸来的~"
})
app.use(router.routes());
app.listen(4000);
思维导图:
更多推荐
所有评论(0)