跨域

浏览器跨域

引言:

在前后端分离的背景下,不少前端小伙伴在与接口联调的时候,都遇到过跨域的问题,见过类似于blocked by CORS policyOrigin 'xxx' is therefore not allowed access.的报错信息。那为什么会抛出跨域这个异常呢?这就不得不提浏览器的同源策略。

同源策略SOP

其实从我们联网的那一刻起,私人的各种信息就时刻处于暴露的风险中,而Web浏览器作为常用的工具,是我们与各类网站产生信息交互的重要窗口,自然地,窗口中的页面就成了遭遇攻击的主要场所之一。”同源策略“就是为应对安全威胁的一种策略,应用了此策略的浏览器,要求在页面上请求资源的地址必须和当前页面同源;一旦违反了同源策略,就会抛出跨域异常。此时,请求响应的结果会被浏览器拦截,使脚本无法读取返回的数据。

因此简单来说,同源策略的主要目的就是用于防止恶意脚本的攻击,防止恶意网站从其他网站读取机密信息,它是Web应用安全模型中的一个重要概念。

从Wiki上关于同源策略的表述,我们也能看出,同源策略能够防止某个网页上的恶意脚本访问另一个网页上的敏感数据。

This policy prevents a malicious script on one page from obtaining access to sensitive data on another web page through that page's Document Object Model (DOM).

那有什么漏洞可以使恶意脚本获取到数据呢?Wiki里也简单描述了一下:

This mechanism bears a particular significance for modern web applications that extensively depend on HTTP cookies[1] to maintain authenticated user sessions, as servers act based on the HTTP cookie information to reveal sensitive information or take state-changing actions.

这种机制对于广泛依赖HTTP cookies来维护验证过的用户会话的现代Web应用程序具有特别重要的意义,因为服务器会根据HTTP cookie信息泄露敏感信息或采取改变状态的行为。

换句话说就是,恶意脚本通过cookie,就可能有机会拿到敏感信息,或者篡改数据,进行比如CSRF(跨站请求伪造)攻击。

同源策略限制的内容

既然要防止网站内容被窃取和篡改,就要先知道网站的数据源,然后对他们进行访问限制,防止被恶意脚本(js)读取。

首先,在前后端分离的背景下,异步请求是一个重要数据来源,通过异步请求可以直接获取最新数据;

其次,本地存储也是重要的数据源,一些用户操作的结果会存储在本地;

最后,就是直接获取页面上数据,一般就是指操作iframe嵌入的页面。

简单来说就是:

  1. XHR/Fetch异步请求返回的数据
  2. cookie、Web Storage、indexDB等本地存储
  3. iframe嵌入的页面内容

同源策略主要拦截的就是对于这些数据的读取和修改。

本地存储中的数据(Web Storage和indexDB等)是按源来分开存储的,每个源都有独立的存储空间。

其中cookie的同源判断规则略有不同,可以使用域、路径、安全性和HttpOnly标志限制其可用性。domain和path一起来限制cookie能被哪些URL访问,即如果请求数据的网页(也就是源站)与cookie的Domain或其子域匹配、并且页面URL的路径与Path或子路径匹配,则可以访问该cookie;但如果某个cookie设置了httpOnly,客户端就无法通过js代码去访问这个cookie。

MDN: Cross-origin data storage access

When you set a cookie, you can limit its availability using the Domain, Path, Secure, and HttpOnly flags.

允许跨域加载资源的标签

前面说了,同源策略限制的是恶意脚本的攻击,所以同源策略只应用于脚本。

It is very important to remember that the same-origin policy applies only to scripts. This means that resources such as images, CSS, and dynamically-loaded scripts can be accessed across origins via the corresponding HTML tags[2] (with fonts being a notable exception[3]). Attacks take advantage of the fact that the same origin policy does not apply to HTML tags.

所以图片、CSS和动态加载的脚本等资源可以通过相应的HTML标签来跨源访问;同源策略不适用于 HTML 标签。所以像是img标签的src属性、link标签的href属性、script标签的src属性以及iframe的src属性这些都可以获取到跨源的资源。所以有时这些标签会被利用来进行攻击。

同源判断的规则

那怎么样可以判断两个网页是同源的呢?

在RFC 6454的第四节中,规定了用于计算URI的“源”的算法。

对于绝对URI,源是三元组:scheme、host、port;如果URI不是分层的,或者不是绝对URI,就使用全球唯一标识符。

只有这些值完全相同时,两个资源才被视为同源。

就我们日常开发来说,可以简单地判断,协议、主机名、端口号三者相同,就可以认为两个网页是同源。主机名需要完全匹配。

常见的解决跨域的方法

至此我们就对同源策略有了大致了解。

虽然同源策略可以保障数据的安全性,但在前后端分离的大背景下,以及大型网站多个子域间数据通信和数据互联互通的需求下,有时候我们不得不进行跨域访问,那此时要如何去突破这个策略的限制呢?现代浏览器支持多种技术,以可控的方式放宽同源策略。

Wiki列出了以下几种技术

  • 片段标识符或window.name

    起初,人们使用片段标识符或window.name属性等变通方法在不同域的文档之间传递数据。

  • 数据污点

    这个之前我没怎么了解过,大概是类似tag、标签之类的东西

  • document.domain属性

    为确保两个页面可以互相读取数据,可以将两个页面的document.domain属性设置为相同值,但需要注意,这样操作会隐式地将端口设置为空

  • JSONP(JSON with Padding)

    这应该属于比较老式的一种方法。只支持get请求,利用了script标签可获取跨域资源的特点,从而避开同源策略的限制。

    具体操作:使用此方法时,需要前端先进行函数的声明定义,通常这个函数需要接收参数;然后请求服务端得到的响应内容是JS代码,其中包含了先前已定义的函数的调用,并且会进行参数的传递(以JSON字符串的方式);浏览器加载这段脚本时,会对此代码进行解析并执行,也就是调用预定义函数来处理JSON数据。

    因为只支持get请求,所以使用比较局限;另外我们也可以看出,使用JSONP会增加前端和服务端的耦合度,服务端不仅要了解前端需要的数据,还要了解在前端定义的函数。

  • CORS(Cross-Origin Resource Sharing)

    跨域资源共享是一种基于 HTTP 头的机制,该机制通过允许服务器标示除了它自己以外的其它(域、协议和端口),使得浏览器允许这些 origin 访问加载自己的资源。

    此机制新增了一组HTTP首部字段(header)来解决跨域问题,允许服务器声明哪些源站通过浏览器有权限访问哪些资源。

    这个方法主要需要后端接口做处理,使响应报文包含正确的CORS响应头,如:

    response.setHeader('Access-Control-Allow-Origin', '*'); // 允许的请求来源
    response.setHeader('Access-Control-Allow-Headers', '*'); // 允许携带的头信息,用于预检请求的响应
    response.setHeader('Access-Control-Allow-Methods', '*'); // 允许的请求类型,用于预检请求的响应
    response.setHeader('Access-Control-Expose-Headers', 'token'); // 允许浏览器可以拿到的自定义响应头
    

    简单来说,就是规定了指定的源可以获取到的数据以及可以发送的数据。

    CORS中涉及了“预检请求”的使用,用于获知服务端是否允许该跨源请求,更详细的内容可以查看MDN: CORS

    简单来说,CORS就是根据HTTP头部定义的规则,允许脚本读取接口返回的数据。

  • 跨文档通信

    这种技术允许一个页面上的脚本向另一个页面上的脚本传递文本信息,而不管脚本来自何处。

    调用Window对象上的postMessage()方法会异步触发对应窗口中的“onmessage”事件,并触发任何用户定义的事件处理程序。

    这样两个页面就可以通过这种消息传递技术进行安全通信;这可以用于解决不同iframe之间的跨域交互。

  • Websocket

    Wiki中的这段描述:

    现代浏览器允许脚本连接到WebSocket地址。

    当使用WebSocket URI时,浏览器会识别并在请求中插入一个Origin:header,标明请求连接的脚本的来源。为确保跨站点安全性,WebSocket服务器必须将头部数据与允许接收回复的源列表进行比较。

    虽然我没用过WebSocket来解决跨域问题,但根据这段描述,我感觉跟CORS的实现原理其实有点类似,判断请求源是否是允许交互的源。

  • 代理跨域

    除了以上几种,我们还可以通过设置代理来避开同源策略。比如当我们使用Vue或者React的官方脚手架进行项目开发时,可以在各类构建工具中配置Proxy。

    因为应用同源策略的是Web浏览器,所以绕开浏览器就可以拿到数据。

    比如使用Webpack作为构建工具时,可以对devServer.proxy进行代理配置,使满足特定规则的请求,被转发到真实的接口地址,相当于给真实的接口做了一层代理。

    module.exports = {
      // ...
      devServer: {
        proxy: {
          '/api': {
            target: 'http://www.example.com',
            pathRewrite: {
              '^/api': ''
            }
          }
        }
      }
    };
    

    假设此时在A页面http://locahost:8080/index.html发起了一个请求http://localhost:8080/api/getList,请求实际会被转发至http://www.example.com/getList,这样实际请求的数据会返回给代理,再由代理返回给Web浏览器,就避开了同源策略的限制,页面脚本就可以拿到数据。

总结

总结一下,同源策略是Web安全模型中的重要概念,主要就是防止恶意脚本获取数据,所以HTML标签访问和加载跨源数据并不会受到限制;并且控制的主要是读取的操作。

出于数据互联互通的需求,现代浏览器也支持多种技术,以可控的方式放宽同源策略,我们可以根据具体项目的情况选择合适的技术,当然CORS是目前应用比较多的(就我所接触的而言)。

posted @ 2022-12-09 14:54  beckyye  阅读(122)  评论(0编辑  收藏  举报