几种常用跨域方式区别:
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);

思维导图:
在这里插入图片描述

Logo

技术共进,成长同行——讯飞AI开发者社区

更多推荐