从原理上认识跨域, 以及如何解决跨域问题

跨域资源共享

欢迎大家关注微信公众号后端开发杂谈, 这里有更多实用的干货等着你来拿!

简介: 在日常的前端开发过程中, 总是会请求多个的域名. 那么随之而来就存在跨域共享资源的问题. 本文带你从原理上去认识什么状况下会产生跨域, 以及这些跨域下的请求有何特点, 以及如何去解决跨域带来的问题.


跨域资源共享(CORS)

跨域资源共享(CORS)是一种机制, 它使用额外的 HTTP头 来告诉浏览器让运行在一个origin(domain)上的Web应用被准许访问来自不同源服务器上的指定的资源. 当一个资源从 与该资源本身所在的服务器不同的域,协议或端口 请求另外一个资源时, 该资源会发起一个跨域 HTTP 请求.

例如: 前端主机: www.fontend.com, 后端主机: www.backend.com. 当 前端后端 发送API请求的时候, 由于域名不同, 导致该请求是一个跨域请求.

处于安全考虑, 浏览器限制从脚本内发起的跨域HTTP请求. 例如,XMLHttpRequest和Fetch API遵循同源策略. 这意味着使用这些API的Web应用程序只能从加载应用程序的同一个域请求HTTP资源,除非使用CORS头文件.

在这里插入图片描述

跨域资源共享(CORS) 机制允许 Web 应用服务器进行跨域访问控制, 从而使跨域数据传输得以安全进行. 现代浏览器支持在 API 容器中(例如 XMLHttpRequest 或 Fetch)使用CORS, 以降低跨域HTTP请求所带来的风险.


使用CORS的情景

  • 由 XMLHttpRequest 或 Fetch 发起的跨域 HTTP 请求
  • Web 字体(CSS中通过 @font-face 使用跨域字体资源), 网站可以发布字体资源, 并只允许授权网站跨站调用.
  • WebGL 贴图. 例子: WebGI贴图
  • 使用 drawImage 将 Images/video 画面绘制到 canvas
  • 样式表 (使用CSSOM). CSS属性 scroll-behavior. 案例

跨域请求规范

对那些可能对服务器数据产生副作用的 HTTP 请求方法 (特别是GET以外的HTTP请求, 或者搭配某些MIME类型的POST请求), 浏览器必须首先使用OPTIONS方法发起一个预检请求 (preflight request), 从而获知服务端是否允许该跨域请求. 服务器确认允许之后, 才发起实际的HTTP请求. 在预检请求的返回中, 服务器端也可以通知客户端, 是否需要携带身份凭证(包括Cookies 和HTTP认证相关数据).

跨域涉及的场景

简单请求

在使用 XMLHttpRequest 发起的请求中, 某些请求不会触发 CORS 预检测请求(OPTIONS请求), 这样的请求称为 ‘简单请求’. 该术语并不属于 Fetch (其中定义了CORS)规范.

请求满足下面所有条件, 则该请求可视为 ‘简单请求’:

  • 请求方法是下列之一:

    • GET
    • POST
    • HEAD
  • 对CORS安全的首部字段集合, 仅允许自定义下列请求头:

    • Accept
    • Accept-Language
    • Content-Language
    • Content-Type 的值为 text/plain, multipart/form-dataapplication/x-www-form-urlencoded
    • DPR
    • Downlink
    • Save-Data
    • Width
    • Viewport-Width
  • 请求中任意 XMLHttpRequestUpload 对象没有注册任何事件监听器; XMLHttpRequestUpload 对象可以使用XMLHttpRequest.upload 属性访问. (XMLHttpRequestUpload 用来表示上传的进度, 这个对象是不透明的, 但是作为一个XMLHttpRequestEventTarget, 可以通过对其绑定事件来追踪它的进度.)

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

简单请求跨域(没有预检请求), 服务器的响应头包括以下内容:

Access-Control-Allow-Origin: <origin> | * # 必须

Access-Control-Request-Methods: 'POST,GET,HEAD' # 可选(实际请求所允许使用的 HTTP 方法)

Access-Control-Request-Headers: 'Authorization,Content-Type' # 可选(实际请求中允许携带的header)

  • 简单请求跨域的响应头, 只能是"简单响应头", 只能包括以下的响应头(1个或多个).
- Cache-Control
- Content-Language
- Content-Type
- Expires
- Last-Modified
- Pragma

任何其他响应头都是禁止的. 注意: 没有 Content-Length, 要允许js访问任何其他响应头, 服务器必须在响应头中设置 Access-Control-Expose-Headers:Content-Length


预检请求(非简单请求)

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

请求满足下列任一条件时, 即应首先发送预检请求:

  • 使用了下面任一HTTP方法:

    • PUT
    • DELETE
    • CONNECT
    • OPTIONS
    • TRACE
    • PATCH
  • 人为设置了 对CORS安全的首部字段集合之外 的其他首部字段. 该集合是:

    • Accept
    • Accept-Language
    • Content-Language
    • Content-Type 的值不是 text/plain, multipart/form-dataapplication/x-www-form-urlencoded
    • DPR
    • Downlink
    • Save-Data
    • Width
    • Viewport-Width
  • 请求中任意 XMLHttpRequestUpload 对象注册了事件监听器

  • 请求中使用了 ReadableStream 对象.

非简单请求跨域(存在预检请求), 服务器的响应头包括以下内容:

Access-Control-Allow-Origin: '<origin> | *' # 必须

Access-Control-Request-Methods: 'POST,GET,HEAD' # 可选(实际请求所允许使用的 HTTP 方法)

Access-Control-Request-Headers: 'Authorization, Content-Type' # 可选(实际请求中允许携带的header)


凭据(Credentials)

默认情况下, 跨域请求不会携带任何凭据 (cookies 或者 HTTP认证(HTTP authentication) )

要想携带凭证头, 需要明确设置相关参数:

  • 对于 Fetch, 需要添加 credentials: "include" 参数.

携带凭据的跨域(存在预检请求), 服务器的响应头包括以下内容:

Access-Control-Allow-Origin: '<origin>, 必须

Access-Control-Allow-Credentials: true # 必须

Access-Control-Request-Methods: 'POST,GET,HEAD' # 可选(实际请求所允许使用的 HTTP 方法)

Access-Control-Request-Headers: 'Authorization, Cookie' # 必须(实际请求中允许携带的header)


预检请求中携带重定向的问题

大多数浏览器不支持针对于预检请求的重定向.如果一个预检请求发生了重定向, 浏览器将报告错误:

The request was redirected to 'https://example.com/foo', which is disallowed  for cross-origin 
requests that require preflight.

Request requires preflight, which is disallowed to follow cross-origin redirect.

解决方法:

  • 在服务端去掉预检请求的重定向
  • 将实际请求变成一个简单请求

附带身份凭证的请求

FetchCORS 的一个特性是, 可以基于 HTTP cookiesHTTP认证信息 发送身份凭证. 一般而言, 对于跨域 XMLHttpRequestFetch 请求, 浏览器 不会 发送身份凭证信息. 如果要发送凭证信息, 需要设置 XMLHttpRequest 的某个特殊标志位.

附带身份凭证的请求与通配符

对于附带身份凭证的请求, 服务器不得设置 Access-Control-Allow-Origin 的值为 "*".

这是因为请求的首部中携带了 Cookie 信息, 如果 Access-Control-Allow-Origin 的值是 "*", 请求将会失败, 而将 Access-Control-Allow-Origin 的值设置为特定的 origin, 则请求将成功执行.

还有, 响应首部中也携带了 Set-Cookie 字段, 尝试对 Cookie 进行修改, 如果操作失败, 将会抛出异常.


跨域相关 HTTP 响应首部字段

  • Access-Control-Allow-Origin
Access-Control-Allow-Origin: <origin> | *

其中, origin 参数的值指定了允许访问该资源的外域 URI. 对于不需要携带身份凭证的请求, 服务器可以指定该字段的值为通配符, 表示允许来自所有域的请求.

如果服务端指定了 具体的域名 而非 "*", 那么响应首部中的 Vary 字段的值必须包含 Origin. 这将告诉客户端: 服务器对不同的源站返回不同的内容.

  • Access-Control-Expose-Headers

在跨域访问时, XMLHttpRequest 对象的 getResponseHeader() 方法只能拿到一些最基本的响应, Cache-Control, Content-Language, Content-Type, Expires, Last-Modified, Pragma 等. 如果要访问其他头, 则需要服务器设置本响应头.

Access-Control-Expose-Headers 头让服务器把允许浏览器房屋的头放入白名单. 例如:

Access-Control-Expose-Headers: X-My-Custom-Header, X-Another-Custom-Header

这样浏览器就能够通过 getResponseHeader 访问 X-My-Custom-HeaderX-Another-Custom-Header 响应头了.

  • Access-Control-Max-Age

Access-Control-Max-Age 头指定了 preflight 请求的结果能被缓存多久.

Access-Control-Max-Age: <delta-seconds>
  • Access-Control-Allow-Credentials

Access-Control-Allow-Credentials 头指定了当浏览器的 credentials 设置为 true 时, 是否允许浏览器读取 response 的内容. 当用在对 preflight 预检测请求的响应中时, 它指定了实际的请求是否可以使用 credentials.

注意: 简单GET请求不会被预检; 如果对此类请求的响应中不包含该字段, 这个响应会被忽略掉, 并且浏览器也不会将响应内容返回给网页.

  • Access-Control-Allow-Methods

Access-Control-Allow-Methods 头用语预检测请求的响应. 其指明了实际请求所允许使用的 HTTP 方法.

Access-Control-Allow-Methods: <method>[, <method>] | *
  • Access-Control-Allow-Headers

Access-Control-Allow-Headers 头用于预检测请求的响应. 其指明了实际请求中允许携带的首部字段.

Access-Control-Allow-Headers: <field-name>[, <field-name>] | *

感谢大家的支持, 欢迎关注公众号 “后端开发杂谈”, 每天领取新文章.
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值