跨源资源共享(CORS)这块狗皮膏药该撕掉了!

老人已经84天没有打到鱼了,但他仍然选择出海。

通过本文你将学到什么?

  • CORS解决跨域的几种模式
  • 根据实例为你展示响应首部字段的意义
  • 了解跨域的 OPTIONS 请求

相关资料

涉及资料就不往文档里放了,在这里贴出

跨域环境准备

准备前端服务:http://localhost:8080
准备接口服务:http://localhost:3000 (koa2)

简单请求 的跨域

定义

“简单请求”请求不会触发 CORS 预检请求,若请求满足所有下述条件,则该请求可视为“简单请求”:

  1. 使用以下方法之一:

    • GET
    • POST
    • HEAD
  2. 除了被用户代理自动设置的首部字段(例如 ConnectionUser-Agent)和在 Fetch 规范中定义为 禁用首部名称 的其他首部,允许人为设置的字段为 Fetch 规范定义的 对 CORS 安全的首部字段集合。该集合为:

    • Accept
    • Accept-Language
    • Content-Language
    • Content-Type (需要注意额外的限制)
    • DPR
    • Downlink
    • Save-Data
    • Viewport-Width
    • Width
  3. Content-Type 的值仅限于下列三者之一:

    • text/plain
    • multipart/form-data
    • application/x-www-form-urlencoded
  4. 请求中的任意 XMLHttpRequestUpload 对象均没有注册任何事件监听器;XMLHttpRequestUpload 对象可以使用 XMLHttpRequest.upload 属性访问。(本文不探讨)

  5. 请求中没有使用 ReadableStream 对象。(本文不探讨)

来一个场景模拟

前端 get 请求

this.$axios.get('http://localhost:3000', {
    ...
}).then(res => {
  console.log(res)
})
复制代码

服务端代码:

const Koa = require("koa");
const debug = require('debug')('corsTest:');
const app = new Koa();

app.use(async (ctx, next) => {
  ctx.body = "你好,koa";
  next()
});

app.listen(3000);
复制代码

浏览器 ?:

image.png
意思:所请求的资源上没有 Access-Control-Allow-Origin 标头

服务端处理办法
这种场景只需服务端设置响应头 Access-Control-Allow-Origin

const Koa = require("koa");
const debug = require('debug')('corsTest:');
const app = new Koa();

app.use(async (ctx, next)=> {
  // 设置服务可支持的 origin,
  // * 支持所有 origin
  ctx.set('Access-Control-Allow-Origin', '*');
  await next();
});

app.use(async (ctx, next) => {
  ctx.body = "你好,koa";
  next()
});

app.listen(3000);
复制代码

浏览器结果 ? ? :

image.png


非简单请求 的跨域

不满足 简单请求 定义条件的都是 非简单请求, 上文定义中已说明。

场景一:非限定的 Content-Type

前端 post 请求

this.$axios.post('http://localhost:3000/create', {
      ...
}).then(res => {
  console.log(res)
})
复制代码

服务端增加一个 post 接口:

const Koa = require("koa");
const debug = require('debug')('corsTest:');
const app = new Koa();

app.use(async (ctx, next)=> {
  // 设置服务可支持的 origin
  ctx.set('Access-Control-Allow-Origin', '*');
  await next();
});

app.use(async (ctx, next) => {
  if (ctx.method === 'POST') { 
    switch(ctx.url) {
      case '/create': //增加一个接口
        ctx.body = { message: "请求成功"}
        break;
      default:;
    }
  }
  if (ctx.method === 'GET') {
    ctx.body = "你好,koa";
  }
  next()
})

app.listen(3000);
复制代码

浏览器结果 ? :

image.png
ps: 这里有同学会疑问?️,前面不是说到 get 、 post 、 head 这三种类型为简单请求吗?这个 post 请求怎么还跨域了呢?

⚠️注意报错信息:

has been blocked by CORS policy: Request header field content-type is not allowed by Access-Control-Allow-Headers in preflight response.

意思:在预请求的 Access-Control-Allow-Headers 响应首部中 content-type 的设置是不支持的!

这也应证的 非简单请求的定义中的第三条,由于当前请求 content-type 值包含 application/json(axios 插件默认设置),不在规定三种范围内。

服务端处理办法

app.use(async (ctx, next)=> {
  // 设置服务可支持的 origin
  ctx.set('Access-Control-Allow-Origin', '*');
  // 设置服务可支持的请求首部名字
  ctx.set('Access-Control-Allow-Headers', 'Content-Type, Content-Length, Authorization, Accept, X-Requested-With');
  await next();
});
复制代码

浏览器 ??:
image.png

场景二: 非简单请求 Method

前端发送 DELETE 请求:

this.$axios.delete('http://localhost:3000', {
      ...
}).then(res => {
  console.log(res)
}) 
复制代码

服务端代码增加:

app.use(async (ctx, next) => {
  if (ctx.method === 'POST') {
    switch(ctx.url) {
      case '/create':
        ctx.body = { message: "请求成功"}
        break;
      default:;
    }
  }
  if (ctx.method === 'GET') {
    ctx.body = "你好,koa";
  }
  if (ctx.method === 'DELETE') { //增加一个 delete 请求
    ctx.body = "你好,koa delete";
  }
  next()
})
复制代码

浏览器结果 ??:
image.png
意思:在预请求的 Access-Control-Allow-Methods 响应首部中 DELETE 是不支持的!

这也应证的 非简单请求的定义中的第一条, 请求方法不在 简单请求 规定范围内。

服务端处理办法

app.use(async (ctx, next)=> {
  // 设置服务可支持的 origin
  ctx.set('Access-Control-Allow-Origin', '*');
  // 设置服务可支持的请求首部方法
  ctx.set('Access-Control-Allow-Methods', 'PUT, POST, GET, DELETE, OPTIONS');
  await next();
});
复制代码

浏览器 ???:

image.png

场景三: 自定义请求首部字段

前端请求:

this.$axios.get('http://localhost:3000', {
      headers: {
        custom: 11
      }
    }).then(res => {
      console.log(res)
    })
复制代码

服务端: 上面代码不做变动

浏览器???:

image.png

⚠️报错信息:

CORS policy: Request header field custom is not allowed by Access-Control-Allow-Headers in preflight response.

意思:在预请求的 Access-Control-Allow-Headers 响应首部中 custom 字段是不支持的!

服务端增加处理办法

app.use(async (ctx, next)=> {
  // 设置服务可支持的 origin
  ctx.set('Access-Control-Allow-Origin', '*');
  // 设置服务可支持的请求首部方法
  ctx.set('Access-Control-Allow-Methods', 'PUT, POST, GET, DELETE, OPTIONS');
  // 设置服务可支持的请求首部名字, 添加一个 custom
  ctx.set('Access-Control-Allow-Headers', 'Content-Type, Content-Length, Authorization, Accept, X-Requested-With, custom');
  await next();
});
复制代码

浏览器 ????:

image.png

附带身份凭证请求 的跨域

如果前端想携带 cookie 发送跨域请求
前端请求:

document.cookie = 'str=test'; // 设置cookie
this.$axios.get('http://localhost:3000', {
      headers: {
        custom: 11
      },
      withCredentials: true // 增加设置
    }).then(res => {
      console.log(res)
    })
复制代码

服务端: 以上代码不做变动

cookie 状态:

image.png

浏览器????:

image.png

⚠️报错信息:

CORS policy: Response to preflight request doesn't pass access control check: The value of the 'Access-Control-Allow-Origin' header in the response must not be the wildcard '*' when the request's credentials mode is 'include'. The credentials mode of requests initiated by the XMLHttpRequest is controlled by the withCredentials attribute.

意思:当前预请求中包含认证模式,响应首部中 Access-Control-Allow-Origin 的值不能为通配符 ‘*’, XMLHttpRequest 发起的认证模式请求被 withCredentials 属性控制 !

服务端增加处理办法

第一步:设置 Access-Control-Allow-Origin 为当前请求 origin
第二步:设置 Access-Control-Allow-Credentials 为 true

app.use(async (ctx, next)=> {
  // 第一步
  const origin = ctx.header['origin'] || '*';
  ctx.set('Access-Control-Allow-Origin', origin);
  // 第二步 
  ctx.set('Access-Control-Allow-Credentials', true)
  // 设置服务可支持的请求首部方法
  ctx.set('Access-Control-Allow-Methods', 'PUT, POST, GET, DELETE, OPTIONS');
  // 设置服务可支持的请求首部名字, 添加一个 custom
  ctx.set('Access-Control-Allow-Headers', 'Content-Type, Content-Length, Authorization, Accept, X-Requested-With, custom');
  await next();
});
复制代码

浏览器 ?????:
image.png

服务端 ctx 上下文已经收到 cookie:
image.png

总结

image.png


扩展

跨域之后服务端还能收到请求吗??

结论在前:

  • 简单请求的跨域: 服务端能收到请求,并返回正常数据;
  • 非简单请求:服务端只能正常收到 OPTIONS 请求;

在和小伙伴讨论这个问题之前,想想自己平时跨域问题出现时,在浏览器里报错红的一批,自然反应就是,请求没有发出去,就算发出去了服务收到我的请求,也没有给我返回数据??

代码验证:
前端代码:

this.$axios.get('http://api.fd***-inc.com', {
      ...
    }).then(res => {
      console.log(res)
    })
复制代码

浏览器表现:
image.png

看响应也是空的:
image.png
我们看到这就是一个简单请求的跨域,而且浏览器查看 response 是没有返回数据的。

这个时候我们需要用抓包工具验证一下,(我用的 Charles ):
image.png

可以看到接口是正常返回数据。


用了挺长时间总结了一下,到这就结束啦!涉及的内容还是挺多的,不对的地方多多指教??

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享