浏览器SOP与CORS

同源策略(SOP)

同源策略(Same origin policy)是浏览器安全模型,是浏览器为了源的安全做出的限制。

源其实就服务器,也就是说,同源策略是通过限制浏览器的行为,来保护服务器的数据,禁止非同源之间窃取对方资源。

例如,“http://127.0.0.1:3000/index.html”的脚本可以访问到“http://127.0.0.1:3000“的资源,但是如果尝试访问”http://127.0.0.1:3001“的资源,那就会报错,浏览器会屏蔽掉3001端口的响应。

同源策略只限制脚本本身的行为,但是不限制静态资源的加载。

例如,可以通过script标签从其它服务器加载jquery文件,但是使用fetch请求就会报错,jsonp就是这个原理,所以CSRF攻击能够通过HTML标签进行。

同源策略除了会屏蔽掉来自非同源的资源外,还会隔离localStorage、sessionStorage、cookie、indexDB,从而保证每个源的数据安全。

接下来我们尝试发送下跨域请求,看看浏览器都有哪些处理。

跨域请求

下面用node启动两个服务器,通过js从3000端口向3001端口发送一个跨域请求。

index.html

<script>
    fetch("http://127.0.0.1:3001/api");
</script>

client.js

 1 const http = require("http");
 2 const fs = require("fs");
 3 const { exec } = require("child_process");
 4 
 5 http.createServer((req, res) => {
 6     const url = req.url;
 7 
 8     if (url === "/") {
 9         fs.createReadStream("index.html").pipe(res);
10     }
11 }).listen(3000);
12 
13 exec("start http://127.0.0.1:3000/");

server.js

 1 const http = require("http");
 2 
 3 http.createServer((req, res) => {
 4     const url = req.url;
 5 
 6     if (url === "/api") {
 7         console.log("收到请求");
 8         return res.end("响应的内容");
 9     }
10 }).listen(3001);

然后查看node,已经接收到了请求,但是浏览器这边报CORS错误。

原因是响应头里没有“Access-Control-Allow-Origin”标头,表示3001端口没有跨域白名单,所以浏览器屏蔽掉了来自3001端口的资源,并显示CORS错误。

下面看下CORS是什么。

跨域资源共享策略(CORS)

上面说SOP的目的是保护服务器的资源,那如果这个资源是公开的呢,或者这个资源有跨域白名单呢?所以CORS就应运而生了。

跨域资源共享策略(Cross-origin Resource Sharing)就是在非同源之间协商共享资源的一种策略。

上面的“Access-Control-Allow-Origin”标头的作用就是公布一个允许跨域的白名单。

现在我们给3001端口添加一个标头,允许任意源的访问。

res.writeHead(200, "ok", {
    "Access-Control-Allow-Origin": "*",
});

可以看到,浏览器接收到这样一个白名单后,就知道了3001端口的信息是公开的,于是把响应的内容暴露给了js。

CORS的具体流程如下:

  1. 浏览器会自动在每个跨域请求中添加Origin头,用于声明请求方的源。
  2. 资源服务器根据请求中Origin标头返回访问控制策略”Access-Control-Allow-Origin“标头,并在其中声明允许读取响应内容的源。
  3. 浏览器检查资源服务器在”Access-Control-Allow-Origin“标头中声明的源,是否与请求方的源相符,如果相符合,则允许请求方脚本读取响应资源,否则屏蔽掉。

另外,根据MDN的文档定义,请求方法为:GET、POST、HEAD,且请求头Content-Type为:text/plain、multipart/form-data、application/x-www-form-urlencoded的就属于简单请求。

我们刚才的请求就是简单请求,而非简单请求会在正式通信之前,增加一次HTTP查询请求,称为"预检"请求(preflight)。

"预检"请求用的请求方法是OPTIONS,表示这个请求是用来询问的。

浏览器先询问服务器,当前网页所在的域名是否在服务器的许可名单之中,以及可以使用哪些头信息字段。只有得到肯定答复,浏览器才会发出正式的XMLHttpRequest请求,否则就报CORS错误。

下面我们发送一个预检请求。

预检请求

我们在给fetch请求添加一个自定义标头,这样就会触发预检请求。

1 <script>
2     fetch("http://127.0.0.1:3001/api", {
3         headers: {
4             xxx: "yyy",
5         },
6     });
7 </script>

刷新后发现报了一个CORS错误,说“Access-Control-Allow-Headers”白名单里没有“xxx”这个标头。

我们在3001端口添加一个白名单试试。

1 res.writeHead(200, {
2     "Access-Control-Allow-Origin": "*",
3     "Access-Control-Allow-Headers": "xxx",
4 });

这次浏览器发现响应标头里有一个标头白名单,预检请求通过了,然后呢向3001端口发送正式请求。

CORS策略是由请求源、请求方式、额外标头或值、本次预检请求有效期、许可证以及客户端需要的其它信息共同约束的,下面是一份CORS标头清单。

CORS标头

Access-Control-Allow-Origin CORS请求的源白名单,要么是一个固定的源:"http://127.0.0.1:3000",要么是"*"。
Access-Control-Allow-Methods 非简单请求的请求方式白名单,例如:"PUT,DELETE"。
 Access-Control-Allow-Headers 非简单请求的请求标头白名单,例如:"test-1,test-2"。
 Access-Control-Max-Age

预检请求的有效期,单位是s,在此期间,正式请求可以直接发送。

Access-Control-Allow-Credentials

一个布尔值,表示是否允许发送Cookie。如果是true,就允许,如果不允许,就不需要该字段。

CORS请求默认不发送Cookie。如果要把Cookie发到服务器,开发者必须在AJAX请求中打开withCredentials属性:xhr.withCredentials=true

需要注意的是,如果要发送Cookie,Access-Control-Allow-Origin就不能设为"*",必须指定明确的源。同时,Cookie依然遵循同源政策,只有用服务器域名设置的Cookie才会上传。

Access-Control-Expose-Headers

CORS请求时,XMLHttpRequest对象的getResponseHeader()方法只能拿到6个基本字段:Cache-ControlContent-LanguageContent-TypeExpiresLast-ModifiedPragma

如果想拿到其他字段,就必须在Access-Control-Expose-Headers里面指定。

Origin CORS请求时浏览器自动添加的当前源。
posted @ 2023-03-17 04:12  万物有序  阅读(109)  评论(0编辑  收藏  举报