“好事”文章推荐:计算机中的文件系统-崩溃恢复机制讲解
文章介绍了操作系统文件系统崩溃恢复机制,包括概念、日志系统特性,详细讲解了 xv6 文件系统写入文件操作步骤、各步骤功能及崩溃恢复情况,还指出 xv6 存在的问题及后续将介绍 Ext3 文件系统。
这篇文章将会带你了解 CORS (Cross-Origin Resource Sharing) 的概念和设置方法,确保您的网站能在遵守同源政策的前提下正确处理跨域请求。学习如何设置 Access-Control-Allow-Methods、Access-Control-Allow-Headers、Access-Control-Allow-Origin 等 header,以及在使用 cookie 时的额外设置。
同源政策 (Same-Origin Policy)
首先我们来认识浏览器的「同源政策」。
大家应该都有用过浏览器提供的 fetch API 或 XMLHttpRequest 等方式,让我们通过 JavaScript 获取资源。常见的应用是向后端 API 获取数据再呈现在前端。
需要注意的是,用 JavaScript 通过 fetch API 或 XMLHttpRequest 等方式发起请求,必须遵守同源政策 (same-origin policy)。
什么是同源政策呢?简单地说,用 JavaScript 访问资源时,如果是同源的情况下,访问不会受到限制;
然而,在同源政策下,非同源的请求则会因为安全性的考量受到限制。浏览器会强制你遵守 CORS (Cross-Origin Resource Sharing,跨域资源访问) 的规范,否则浏览器会让请求失败。
那什么情况是同源,什么情况不是呢?所谓的同源,必须满足以下三个条件:
举例:下列哪个与 https://example.com/a.html 为同源?
不是同源的情况下,就会产生一个跨域 http 请求(cross-origin http请求)。
举个例子,例如我想要在 https://bugbug.io 的页面上显示来自 https://othersite.com 的数据,于是我利用浏览器的 fetch API 发送一个请求:
try {
fetch('https://othersite.com/data')
} catch (err) {
console.error(err);
}
这时候就产生了一个跨域请求。而跨域请求必须遵守 CORS 的规范。
当服务器没有正确设置时,请求就会因为违反 CORS 而失败,在 Chrome DevTool 就会看到以下的经典错误:
Access to fetch at *** from origin *** has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.
我们接下来就一起来看 CORS 到底是什么,又该如何正确地设置 CORS 吧!
什么是 CORS (Cross-Origin Resource Sharing)?
终于要进入重点了,到底什么是 CORS?
简单地说,CORS (Cross-Origin Resource Sharing) 是针对不同源的请求而定的规范,通过 JavaScript 访问非同源资源时,后端服务必须明确告知浏览器允许何种请求,只有后端服务允许的请求能够被浏览器实际发送,否则会失败。
在 CORS 的规范里面,跨域请求有分两种:「简单」的请求和非「简单」的请求。
接下来会分别解释两种请求的 CORS 分别如何运行的。
简单跨域请求
所谓的「简单」请求,必须符合下面两个条件:
Accept
、Accept-Language
、Content-Language
或 Content-Type
(值只能是 application/x-www-form-urlencoded
、multipart/form-data
或 text/plain
)。细节可以看 fetch spec。不符合以上任一条件的请求就是非简单请求。
举个例子来说,下面这个请求不是一个简单的请求:
const response = await fetch('https://othersite.com/data', {
method: 'DELETE',
headers: {
'Content-Type': 'application/json',
'X-CUSTOM-HEADER': '123'
}
});
违反简单请求的地方有三个,分别是:
首先,浏览器发送跨域请求时,会带一个 Origin
header,表示这个请求的来源。
Origin
包含通讯协议、域名和端口三个部分。
所以从 https://shubo.io
发出的往 https://othersite.com/data
的请求会像这样:
GET /data/
Host: othersite.com
Origin: https://shubo.io
...
当后端服务端收到这个跨域请求时,它可以依据「请求的来源」,即 Origin
的值,决定是否要允许这个跨域请求。如果后端服务允许这个跨域请求,它可以「授权」给这个来源的 JavaScript 访问这个资源。
授权的方法是在 response 里加上 Access-Control-Allow-Origin
header:
Access-Control-Allow-Origin: https://shubo.io
如果后端服务允许任何来源的跨域请求,那可以直接回 *
:
Access-Control-Allow-Origin: *
当浏览器收到响应时,会检查请求中的 Origin
header 是否符合响应的 Access-Control-Allow-Origin
header,相符的情况下浏览器就会让这个请求成功,我们也可以顺利地用 JavaScript 读取到响应;反之,则浏览器会将这个请求视为是不安全的而让他失败,即便后端服务确实收到请求也成功地响应了,但基于安全性的原因 JavaScript 中没有办法读到响应。
JavaScript 默认可以访问的「简单」response header 有以下这些:
Cache-Control
Content-Language
Content-Type
Expires
Last-Modified
Pragma
如果要让 JavaScript 访问其他 header,后端服务端可以用 Access-Control-Expose-Headers
header 设置。
X-MY-CUSTOM-HEADER: 123
X-MY-OTHER-CUSTOM-HEADER: 123
Access-Control-Expose-Headers: X-MY-CUSTOM-HEADER, X-MY-OTHER-CUSTOM-HEADER
一般跨域请求
非「简单」的跨域请求,例如:HTTP PUT/DELETE 方法,或是 Content-Type: application/json
等,浏览器在发送请求之前会先发送一个 「preflight请求(预检请求)」,其作用在于先询问服务器:你是否允许这样的请求?真的允许的话,我才会把请求完整发送过去。
什么是 preflight请求呢?
Preflight请求是一个 http OPTIONS 方法,会带有两个请求header:Access-Control-Request-Method
和 Access-Control-Request-Headers
。
Access-Control-Request-Method
: 非「简单」跨域请求的 HTTP 方法。Access-Control-Request-Headers
非「简单」跨域请求带有的非「简单」header。比方说我发送的非「简单」跨域请求是这样:
fetch('https://othersite.com/data/', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CUSTOM-HEADER': '123'
}
})
那我的请求header 会长得会像这样:
POST /data/
Host: othersite.com
Origin: https://shubo.io
Content-Type: application/json
X-MY-CUSTOM-HEADER: 123
浏览器帮我们发送的 preflight请求就会像这样:
OPTIONS /data/
Host: othersite.com
Origin: https://shubo.io
Access-Control-Request-Method: POST
Access-Control-Request-Headers: X-MY-CUSTOM-HEADER, Content-Type
那收到 preflight请求时,后端服务该做什么呢?
后端服务必须告诉浏览器:我允许的方法和 header 有哪些。因此 后端服务的响应必须带有以下两个 header:
Access-Control-Allow-Methods
: 允许的 HTTP 方法。Access-Control-Allow-Headers
: 允许的非「简单」header。当浏览器看到跨域请求的方法和 header 都有被列在允许的方法和 header 中,就表示可以实际发送请求了!
以上面提到例子来说,如果后端服务可以接受上述的请求,后端服务的 preflight response 应该要像这样:
Access-Control-Allow-Methods: POST
Access-Control-Allow-Headers: X-MY-CUSTOM-HEADER, Content-Type
浏览器收到正确的 preflight response,表示 CORS 的验证通过,就可以送出跨域请求了!
接下来,浏览器实际帮我们送出以下的跨域请求:
POST /data/
Host: othersite.com
Origin: https://shubo.io
Content-Type: application/json
X-MY-CUSTOM-HEADER: 123
最后一步,后端服务还是要响应 Access-Control-Allow-Origin
header。浏览器会再检查一次跨域请求的响应是否带有正确的 Access-Control-Allow-Origin
header:
Access-Control-Allow-Origin: https://shubo.io
这一步也检查无误的话,我们的跨域请求才算正式成功喔!这时候我们才能在 JavaScript 中读取响应的内容:
fetch('https://othersite.com/data/', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CUSTOM-HEADER': '123'
}
})
.then(response => response.json())
.then(json => {
console.log(json);
});
跨域请求的 Cookie
一般的 http 请求会带有该域名底下的 cookie;然而,跨域请求默认是不能带 cookie 的。
为什么呢?因为带有 cookie 的请求非常强大,如果请求携带的 cookie 是 session token,那这个请求可以以你的身份做很多危险的事情,像是访问你的隐私数据、从你的银行帐户转帐等。所以浏览器端针对跨域请求的 cookie 也做了规范。
首先,请求必须要明确地标示「我要访问跨域 cookie」。使用 fetch API 和 XMLHttpRequest 的设置方法如下:
credentials
通过 fetch API 发送跨域请求,需要设置 credentials: 'include'
:
fetch('https://othersite.com/data', {
credentials: 'include'
})
withCredentials
通过 XMLHttpRequest 发送跨域请求,需要设置 withCredentials = true;
const xhr = new XMLHttpRequest();
xhr.withCredentials = true;
xhr.open('POST', 'https://othersite.com/data');
如此一来跨域请求就会携带 cookie 了!
后端服务也需要额外的设置:如果是信任的来源,响应要带有 Access-Control-Allow-Credentials
header:
Access-Control-Allow-Credentials: true
如此一来,浏览器才会将 cookie 写进该域名。
注意:如果是允许使用 cookie 的情况,**Access-Control-Allow-Origin
* __不能用 **`****,必须明确标示哪些来源允许访问。** 理由也是基于安全性考量,因为可以用 cookie 的情况下,通常表示会访问一些比较个人化的数据,假设任何网站都能够访问这样的数据,显然是有点危险的!所以不能设为
*`!
如果你偷懒地用了 Access-Control-Allow-Origin: *
,就会无情地收到来自浏览器的错误:
The value of the 'Access-Control-Allow-Origin' header in the response must not be the wildcard '*' when therequest's credentials mode is 'include'. Origin http://localhost:8080 is therefore not allowed access. Thecredentials mode of requests initiated by the XMLHttpRequest is controlled by the withCredentials attribute.
总结
遇到 CORS 的问题,可以归纳出这样的 SOP:
Access-Control-Allow-Origin
header。Access-Control-Allow-Methods
及 Access-Control-Allow-Headers
header。另外,在后端方法本身加上 Access-Control-Allow-Origin
header。credentials: 'include'
或是 withCredentials
参数,后端要加上 Access-Control-Allow-Credentials
header,而且 Access-Control-Allow-Origin
header 不能用 *
。原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。