浏览器跨域技术细节

跨域简介

跨域问题(Cross-Origin)是Web开发中因浏览器的同源策略(Same-Origin Policy) 引发的安全限制,当网页尝试访问与自身源(协议、域名、端口)不同的资源时会被浏览器拦截。

关键点

  • 跨域为浏览器引入,仅在浏览器中存在
  • 同源:一个完整的url组成如下 协议://域名:端口/路由 。 协议、域名、端口均相同为同源

浏览器对不同请求方式跨域限制

  • AJAX 完整的跨域限制
  • img/audio/video 标签,允许跨域加载资源并进行展示,但如果出现跨域且响应资源不允许跨域。则无法在自身站点逻辑中读取到具体的内容。
  • script 能跨域下载并执行脚本,但不能读取内容
  • <link rel="stylesheet"> 能跨域获取并渲染样式,但是无法读取样式内容
  • iframe 发生跨域则无法与内部网页进行交互

如何检测是否允许跨域

检测跨域分为两种方式

  1. 简单请求,直接请求数据,根据响应的响应头来决定是否允许跨域并读取响应内容
  2. 预检请求(Preflight Request):对于非简单请求,会触发使用 OPTIONS 进行预检,满足跨域条件时才会进行真实请求。

什么是简单请求

根据W3C规范,满足以下全部条件的请求为简单请求,不需要进行 OPTIONS 预检

  1. 请求方式必须是以下三种之一
    1. GET
    2. POST
    3. HEAD
  2. 请求头中只能携带以下内容
    1. Accept
    2. Accept-Language
    3. Content-Language
    4. Content-Type ,且值必须为以下内容
      1. application/x-www-form-urlencoded
      2. multipart/form-data
      3. text/plain
  3. 不能使用任何自定义的头(例如: tokenAuthorization 等)

相关的请求头、响应头含义

请求头

  • Origin - 请求发起的来源,所有的请求均需携带。示例值: www.baidu.com
  • Access-Control-Request-Method - 预检请求中使用,表示接下来要发起请求的请求方式。示例值: PUT
  • Access-Control-Request-Headers - 预检请求中使用,表示实际请求中将会携带的自定义请求头。示例值: TOKEN / SIGN

响应头

  • Access-Control-Allow-Origin - 表示允许访问资源的外域,*表示允许所有外域访问。示例值: * / www.baidu.com
  • Access-Control-Allow-Methods - 表示允许外域访问资源的请求方式,在 OPTIONS 请求时,如果允许跨域,将会返回请求时携带的 Access-Control-Request-Method 。示例值: PUT
  • Access-Control-Allow-Headers - 表示允许外域访问资源携带的自定义请求头,在 OPTIONS 请求时,如果允许跨域,将会返回请求时携带的 Access-Control-Request-Headers 。示例值: TOKEN / SIGN
  • Access-Control-Allow-Credentials - 是否允许携带cookie。示例值: true
  • Access-Control-Expose-Headers - 哪些响应头允许被浏览器的JS读取。示例值: Content-Length,Date,Server
  • Access-Control-Max-Age - 预检请求的缓存时间(秒)。示例值: 86400

注意事项

  1. 无论是普通请求,还是预检请求,都需要在响应头中包含 Access-Control-Allow-Origin 。否则就会认为跨域请求。(部分时候前端和后端接口不在同一个域名下时,前端请求后端一个不存在的接口,会优先报CORS错误)
  2. 普通的GET接口,在HEADER中携带上token后,也需要进行预检

示例

  • GET请求,未设置自定义headers,不会发送OPTIONS请求
    • fetch("https://httpbin.org/")
  • GET请求,设置了允许的headers,不会发送OPTIONS请求
    • fetch("https://httpbin.org/", {"headers": {"Accept-Language": "abab"}})
  • GET请求,设置了不允许的headers,会发送OPTIONS请求
    • fetch("https://httpbin.org/", {"headers": {"token": "abc"}})

其他跨域解决方式

JSONP(JSON with Padding)

非标准,早期使用,利用浏览器对 <script> 标签的限制放宽来实现。服务器响应内容为一个javascript的函数调用

浏览器调用方式: <script src="https://api.example.com/data.js"></script>

服务器响应内容: callback({ name: "Alice", age: 20 })

确保页面上提前定义了这个 callback 函数,就能获取数据!

限制:

  • 只能发送GET请求
  • 无法携带自定义请求头

PROXY

因为跨域的同源策略限制只会在浏览器中发生。故可以将跨域调用通过同源的服务器中转调用。从而实现跨域的解决

如 a.com 想要访问 b.com/api 的内容,并且 b 网站不支持跨域。则可以创造一个 a.com/api 的服务器。a.com 直接访问 a.com/api ,a.com/api 在服务器上调用 b.com/api。对于 a.com 来说,就不存在跨域了

参考链接