前端进阶之旅前端进阶之旅
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片NEW
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 业务场景题真实业务问题与追问
  • 查漏补缺常见问题解析
  • AI 模拟面试NEW模拟真实面试 + 报告
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
    • AI 定制路线NEW按你的简历现排
    • AI 知识地图NEW串起全站知识点
  • 动态
    • AI 热点NEWAI 每日动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
AI 助手NEW
旧版
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片NEW
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 业务场景题真实业务问题与追问
  • 查漏补缺常见问题解析
  • AI 模拟面试NEW模拟真实面试 + 报告
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
    • AI 定制路线NEW按你的简历现排
    • AI 知识地图NEW串起全站知识点
  • 动态
    • AI 热点NEWAI 每日动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
AI 助手NEW
旧版
  • JavaScript Part 1

    • Ajax总结篇
    • Canvas 绘制八大行星
    • DOM编程之API学习总结篇
    • JavaScript事件机制
    • JavaScript代码片段100个
    • JavaScript作用域分析总结
    • JavaScript原型链回顾
    • JavaScript及jQuery中的各种宽高属性图解
    • JavaScript工程项目的一系列最佳实践
    • JavaScript常用API合集
    • JavaScript数组、字符串、对象常用方法
    • JavaScript数组方法总结篇
    • JavaScript深浅拷贝
    • JavaScript运动框架之速度时间版本
    • JavaScript运行机制Event Loop
    • JavaScript防抖节流原理
    • Javascript中的复制粘贴功能
    • Javascript数组详解
    • OOP之原型与原型链
    • Object.defineProperty详解
    • V8源码浅析JS数组常见方法
    • await 在 forEach 中不生效解决方案
    • iframe+表单跨域提交POST请求
    • javascript笔记总结篇
    • 业务中处理数据结构常用的JS方法
    • 作用域
    • 你真的掌握变量和类型了吗
    • 前后端分离之数据Mock
    • 原型与原型链
    • 原生JS补给(上)
    • 如何写出一个惊艳面试官的深拷贝
    • 带你填一些JS容易出错的坑
    • 彻底弄懂 JavaScript 执行机制
    • 执行上下文 执行栈
    • 正则回顾总结
    • 正则基础知识
    • 正则完整篇
    • 正则表达式
    • 浅析Promise原理
    • 浅谈JavaScript中的异步处理
    • 深拷贝 vs 浅拷贝
    • 聊一聊typeof instanceof 实现原理.
    • 聊一聊闭包
    • 高阶函数map reduce filter
  • JavaScript Part 2

  • CSS

  • HTML

  • Jquery

  • ES6

  • 小程序

  • Vue

  • React

  • 深入React

  • React Native

  • NodeJS

  • Angular

  • TypeScript

  • Webpack

  • 浏览器

  • 移动端

  • 前端工程化

  • Electron

  • HTTP

  • Nginx

  • Linux

  • 数据结构与算法

  • LeetCode算法题

  • 综合

完整面试题地址:
作者:程序员poetry
扫码关注作者公众号:「前端进阶之旅」 每天分享技术干货
前端进阶之旅公众号二维码

CORS原理及实现|博客系列

# CORS原理及实现

# CORS跨域的原理

跨域资源共享(CORS)是一种机制,是W3C标准。它允许浏览器向跨源服务器,发出XMLHttpRequest或Fetch请求。并且整个CORS通信过程都是浏览器自动完成的,不需要用户参与。

而使用这种跨域资源共享的前提是,浏览器必须支持这个功能,并且服务器端也必须同意这种"跨域"请求。因此实现CORS的关键是服务器需要服务器。通常是有以下几个配置:

  • Access-Control-Allow-Origin
  • Access-Control-Allow-Methods
  • Access-Control-Allow-Headers
  • Access-Control-Allow-Credentials
  • Access-Control-Max-Age

具体可看: https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Access_control_CORS#Preflighted_requests

过程分析:

  • 浏览器先根据同源策略对前端页面和后台交互地址做匹配,若同源,则直接发送数据请求;若不同源,则发送跨域请求。

  • 服务器收到浏览器跨域请求后,根据自身配置返回对应文件头。若未配置过任何允许跨域,则文件头里不包含 Access-Control-Allow-origin 字段,若配置过域名,则返回 Access-Control-Allow-origin + 对应配置规则里的域名的方式。

  • 浏览器根据接受到的 响应头里的 Access-Control-Allow-origin 字段做匹配,若无该字段,说明不允许跨域,从而抛出一个错误;若有该字段,则对字段内容和当前域名做比对,如果同源,则说明可以跨域,浏览器接受该响应;若不同源,则说明该域名不可跨域,浏览器不接受该响应,并抛出一个错误。

另外在CORS中有简单请求和非简单请求,简单请求是不会触发CORS的预检请求的,而非简单请求会。

“需预检的请求”要求必须首先使用 OPTIONS 方法发起一个预检请求到服务器,以获知服务器是否允许该实际请求。"预检请求“的使用,可以避免跨域请求对服务器的用户数据产生未预期的影响。

# CORS的哪些是简单请求?

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

简单回答:

  • 只能使用GET、HEAD、POST方法。使用POST方法向服务器发送数据时,Content-Type只能使用application/x-www-form-urlencoded、multipart/form-data或text/plain编码格式。
  • 请求时不能使用自定义的HTTP Headers

详细回答:

  • (一) 使用下列方法之一

    • GET
    • HEAD
    • POST
  • (二) 人为设置以下集合外的请求头

    • 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
  • 请求中的任意 XMLHttpRequestUpload 对象均没有注册任何事件监听器; XMLHttpRequestUpload 对象可以使用 XMLHttpRequest.upload 属性访问。

  • 请求中没有使用 ReadableStream 对象。

除了上面这些请求外,都是非简单请求。

# CORS的预检请求具体是怎样的?

若是跨域的非简单请求的话,浏览器会首先向服务器发送一个预检请求,以获知服务器是否允许该实际请求。

整个过程大概是:

  • 浏览器给服务器发送一个OPTIONS方法的请求,该请求会携带下面两个首部字段:
    • Access-Control-Request-Method: 实际请求要用到的方法
    • Access-Control-Request-Headers: 实际请求会携带哪些首部字段
  • 若是服务器接受后续请求,则这次预请求的响应体中会携带下面的一些字段:
    • Access-Control-Allow-Methods : 服务器允许使用的方法
    • Access-Control-Allow-Origin: 服务器允许访问的域名
    • Access-Control-Allow-Headers: 服务器允许的首部字段
    • Access-Control-Max-Age: 该响应的有效时间(s),在有效时间内浏览器无需再为同一个请求发送预检请求
  • 预检请求完毕之后,再发送实际请求

这里有两点要注意:

一:

Access-Control-Request-Method没有s

Access-Control-Allow-Methods有s

二:

关于Access-Control-Max-Age,浏览器自身也有维护一个最大有效时间,如果该首部字段的值超过了最大有效时间,将不会生效,而是以最大有效时间为主。

# CORS简单请求的案例

还是在原本JSONP的那个案例上。

我在根目录下新建了一个文件夹cors,并往里面添加了一个index.html文件:

/cors/index.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>CORS</title>
</head>
<body>
  <button id="getName">获取name</button>
  <script src="https://cdn.bootcss.com/axios/0.19.2/axios.min.js"></script>
  <script>
    getName.onclick = () => {
      // 简单请求
      axios.get("http://127.0.0.1:8080/api/corsname");
    }
  </script>
</body>
</html>
@前端进阶之旅: 代码已经复制到剪贴板

为了后面也方便调试,用node简单写了一个前端的本地服务和后端的本地服务。

在根目录下新建client.js文件,并写入:

./client.js:

const Koa = require('koa');
const fs = require('fs');
const app = new Koa();

app.use(async (ctx) => {
  if (ctx.method === 'GET' && ctx.path === '/') {
    ctx.body = fs.readFileSync('./index.html').toString();
  }
  if (ctx.method === 'GET' && ctx.path === '/cors') {
    ctx.body = fs.readFileSync('./cors/index.html').toString();
  }
})
console.log('client 8000...')
app.listen(8000);
@前端进阶之旅: 代码已经复制到剪贴板

在根目录下新建index.html文件,并写入:

./index.html:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Client</title>
</head>
<body>
  <ul>
    <li>
      <a href="/cors">CORS跨域</a>
    </li>
  </ul>
</body>
</html>
@前端进阶之旅: 代码已经复制到剪贴板

(以上:实现了一个简单的前端路由效果)

在根目录下新建server.js文件,并写入:

./server.js:

const Koa = require('koa');
const app = new Koa();

app.use(async (ctx, next) => {
  ctx.set("Access-Control-Allow-Origin", ctx.header.origin);
  // ctx.set("Access-Control-Allow-Methods", "PUT,POST,GET,DELETE,OPTIONS");
  // ctx.set("Access-Control-Request-Method", "PUT,POST,GET,DELETE,OPTIONS");
  ctx.set(
    "Access-Control-Allow-Headers", 
    "Origin, X-Requested-With, Content-Type, Access, cc"
  )
  if (ctx.method === 'OPTIONS') {
    ctx.status = 204;
    return;
  }
  await next();
  if (ctx.path === '/api/corsname') {
    ctx.body = {
      data: 'LinDaiDai'
    }
    return;
  }
})
console.log('server 8080...')
app.listen(8080);
@前端进阶之旅: 代码已经复制到剪贴板

并给package.json中配置两个启动指令:

package.json:

{
  "scripts": {
  	"client": "node ./client.js",
  	"server": "node ./server.js"
	}
}
@前端进阶之旅: 代码已经复制到剪贴板

OK👌,来分别启动一下npm run client和npm run server

并打开页面的127.0.0.1:8000/cors(或者打开127.0.0.1:8000然后点击CORS这个a标签)

点击获取name按钮,可以看到能够正常获取到本地服务器的数据了。

# CORS非简单请求的案例

接着让我们来改造一下./cors/index.html中的按钮点击请求,让它变成一个非简单请求:

./cors/index.html:

getName.onclick = () => {
  // 简单请求
  // axios.get("http://127.0.0.1:8080/api/corsname");

  // 非简单请求
  axios.get('http://127.0.0.1:8080/api/corsname', {
    headers: {
      cc: 'lindaidai'
    }
  })
}
@前端进阶之旅: 代码已经复制到剪贴板

此时,打开页面点击按钮会发现发送了两次corsname的请求:

(一)预检请求:

(二)实际请求:

# CORS附带身份凭证的案例

对于跨域 XMLHttpRequest 或 Fetch 请求,浏览器不会发送身份凭证信息。如果要发送凭证信息,需要设置 XMLHttpRequest 的某个特殊标志位。

例如我们想要在跨域请求中带上cookie,需要满足3个条件:

  • web(浏览器)请求设置withCredentials为true
  • 服务器设置首部字段Access-Control-Allow-Credentials为true
  • 服务器的Access-Control-Allow-Origin不能为*

所以为了模拟这个效果,让我们来写一个小小的登录+获取数据的功能吧。

首先对于web端,我新增了一个登录按钮,并且配置了一下axios:

./cors/index.html:

← 用JS开发桌面应用总结Cookie篇 →

fe
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片NEW
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 业务场景题真实业务问题与追问
  • 查漏补缺常见问题解析
  • AI 模拟面试NEW模拟真实面试 + 报告
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
    • AI 定制路线NEW按你的简历现排
    • AI 知识地图NEW串起全站知识点
  • 动态
    • AI 热点NEWAI 每日动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
AI 助手NEW
旧版
  • JavaScript Part 1

    • Ajax总结篇
    • Canvas 绘制八大行星
    • DOM编程之API学习总结篇
    • JavaScript事件机制
    • JavaScript代码片段100个
    • JavaScript作用域分析总结
    • JavaScript原型链回顾
    • JavaScript及jQuery中的各种宽高属性图解
    • JavaScript工程项目的一系列最佳实践
    • JavaScript常用API合集
    • JavaScript数组、字符串、对象常用方法
    • JavaScript数组方法总结篇
    • JavaScript深浅拷贝
    • JavaScript运动框架之速度时间版本
    • JavaScript运行机制Event Loop
    • JavaScript防抖节流原理
    • Javascript中的复制粘贴功能
    • Javascript数组详解
    • OOP之原型与原型链
    • Object.defineProperty详解
    • V8源码浅析JS数组常见方法
    • await 在 forEach 中不生效解决方案
    • iframe+表单跨域提交POST请求
    • javascript笔记总结篇
    • 业务中处理数据结构常用的JS方法
    • 作用域
    • 你真的掌握变量和类型了吗
    • 前后端分离之数据Mock
    • 原型与原型链
    • 原生JS补给(上)
    • 如何写出一个惊艳面试官的深拷贝
    • 带你填一些JS容易出错的坑
    • 彻底弄懂 JavaScript 执行机制
    • 执行上下文 执行栈
    • 正则回顾总结
    • 正则基础知识
    • 正则完整篇
    • 正则表达式
    • 浅析Promise原理
    • 浅谈JavaScript中的异步处理
    • 深拷贝 vs 浅拷贝
    • 聊一聊typeof instanceof 实现原理.
    • 聊一聊闭包
    • 高阶函数map reduce filter
  • JavaScript Part 2

  • CSS

  • HTML

  • Jquery

  • ES6

  • 小程序

  • Vue

  • React

  • 深入React

  • React Native

  • NodeJS

  • Angular

  • TypeScript

  • Webpack

  • 浏览器

  • 移动端

  • 前端工程化

  • Electron

  • HTTP

  • Nginx

  • Linux

  • 数据结构与算法

  • LeetCode算法题

  • 综合