1. 同源策略
概念:同源策略是一个安全策略,它用于限制从一个源加载的文档或脚本如何与来自另一个源的资源进行交互。它能防止恶意脚本的攻击。
同源策略决定了两个资源只有具有同源才能进行交互。
1.1 同源的定义
如果两个 URL 的协议、主机和端口三者都匹配时,则这两个URL 是同源。
同源的例子:
http://ex.com/app1/index.html http://ex.com/app2/index.html | 源相同,因为有相同的协议和域名 |
http://Example.com:80 http://Example.com | 源相同,因为默认情况下,服务器通过端口号80传递 HTTP 内容 |
不同源的例子:
http://ex.com/app1 https://ex.com/app2 | 源不同,因为协议不同 |
http://ex.com http://www.ex.com http://my.ex.com | 源不同,虽然协议相同,但域名不同 |
http://ex.com http://ex.com:8080 | 源不同,因为端口号不同。第一个端口号是80,第二个是8080 |
2. 跨源网络访问
同源策略控制不同源之间的交互,这些交互通常分为三类:
- 跨域写操作:一般是允许的。例如链接,重定向以及表单提交。
- 跨域资源嵌入:一般是允许的(后面会举例说明)。
- 跨域读操作:一般是不被允许的。
以下是可能嵌入跨源的资源的示例:
<script src="..."></script>
标签嵌入跨域脚本。<link rel="stylesheet" href="...">
标签嵌入跨域 CSS。- 通过
<img>
标签展示的跨域图片。 - 通过
<video>
和<audio>
播放的多媒体资源。 - 通过
<object>
、<embed>
和<applet>
嵌入的插件。 - 通过
@font-face
引入的字体。 - 通过
<iframe>
载入的任何资源。站点可以使用 X-Frame-Options 消息头来阻止这种形式的跨域交互。
上述示例中的跨域操作一般是被允许的,不受同源策略的影响。
2.1 实现跨域请求
2.1.1 图像的 Ping 技术
图像 Ping 跨域请求技术是使用 <img>
标签的 src
属性让一个网页可以从任何网页中加载图像,不受同源策略影响。所以,我们可以动态地创建图像,使用它的 onload
和 onerror
事件来确定是否接受到了响应。
实例:动态创建图像,发起跨域 GET 请求
var img = new Image();
img.onload = img.onerror = function () {
console.log('已完成...');
}
img.src = "https://img-blog.csdnimg.cn/20201113115524230.png" // 设置 src 时发起 GET 请求
缺点:只能发送 Get 请求,无法访问服务器的响应文本。因此,这两种方式只能用于浏览器与服务器的单向通信。
2.1.2 JSONP 技术
网页通过添加一个 <script>
元素,向服务器请求 JSON 数据,这种做法不受同源政策限制;服务器收到请求后,将数据放在用户指定名字的回调函数里传回来。
实例:
// 客户端
window.onload = function () {
addScriptTag('http://127.0.0.1:8888/jsonp?callback=fun'); // 通过查询参数 callback 指定回调函数的名称为 fun
// 或
addScriptTag('http://127.0.0.1:8888/jsonp?jsonp=fun'); // 查询参数 jsonp 也可以指定回调函数的名称
}
function addScriptTag(src) {
var script = document.createElement('script');
script.src = src;
document.body.appendChild(script);
}
function fun(data) {
console.log(typeof data); // 响应内容自动被解码为 JS 对象格式
console.log(data); // 获取响应内容
}
// 服务端 Node.js
app.get('/jsonp', function(req, res) {
res.send('fun({"name":"cez", "age":22})'); // 把响应内容传入回调函数
})
当使用 JSONP 技术时,响应内容必须用指定的回调函数包裹起来。
缺点:只能发送 Get 请求。
2.1.3 CORS
CORS 是一个 W3C 标准,全称是 “跨域资源共享”,它 是 HTTP 的一部分,它允许浏览器向跨源服务器发出 XMLHttpRequest 请求,从而克服了 AJAX 只能同源使用的限制。
// 指定了哪些源可以访问服务器的资源(必需)
res.header('Access-Control-Allow-Origin', '*')
// 指定服务器支持的头信息字段(非必需)
res.header('Access-Control-Allow-Headers', 'Authorization,X-API-KEY, Origin, X-Requested-With, Content-Type, Accept, Access-Control-Request-Method' )
// 服务器支持跨域请求的方法(非必需)
res.header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, PATCH, PUT, DELETE')
3. 字段详解
3.1 Access-Control-Allow-Origin
该字段指定了哪些源可以访问服务器的资源。
语法:
Access-Control-Allow-Origin: *
Access-Control-Allow-Origin: <origin>
实例:
// 如需允许所有资源都可以访问您的资源,您可以如此设置:
Access-Control-Allow-Origin: *
// 如需允许https://developer.mozilla.org访问您的资源,您可以设置:
Access-Control-Allow-Origin: https://developer.mozilla.org
3.2 Access-Control-Expose-Headers
响应头部 Access-Control-Expose-Headers
列出了哪些首部可以作为响应的一部分暴露给外部。
默认情况下,只有七种可以暴露给外部:
Cache-Control
Content-Language
Content-Length
Content-Type
Expires
Last-Modified
Pragma
如果想要让客户端可以访问到其它的头部信息,可以将它们在 Access-Control-Expose-Headers
里面列出来。
语法:
Access-Control-Expose-Headers:<xx>,<xx>,...
实例:
// 想要暴露一个非简单响应首部,可以这样指定:
Access-Control-Expose-Headers: Content-Length
// 想要额外暴露自定义的首部,例如 X-Kuma-Revision,可以指定多个,用逗号隔开:
Access-Control-Expose-Headers: Content-Length, X-Kuma-Revision
3.3 Access-Control-Allow-Headers
如果浏览器请求包括 Access-Control-Request-Headers 字段,则 Access-Control-Allow-Header 字段是必需的。它表明服务器支持的所有头信息字段,不限于浏览器在 预检 中请求的字段。
语法:
Access-Control-Allow-Headers: *
Access-Control-Allow-Headers: <header-name>, xxx, ...
实例:
// 自定义的请求头
Access-Control-Allow-Headers: X-Custom-Header
// 支持多个标头
Access-Control-Allow-Headers: X-Custom-Header, Upgrade-Insecure-Requests
3.4 Access-Control-Allow-Methods
该字段在对预检请求的应答中明确了服务器支持的所有跨域请求的方法。
语法:
Access-Control-Allow-Methods: <method>, <method>, ...
实例:
Access-Control-Allow-Methods: POST, GET, OPTIONS
3.5 Access-Control-Allow-Credentials
该字段是一个布尔值,表示是否允许发送 Cookie。默认情况下,Cookie 不包括在 CORS 请求之中。设为 true,即表示服务器明确许可,Cookie 可以包含在请求中,一起发送给服务器。
3.5.1 withCredentials 属性
上面说到,CORS 请求默认不发送 Cookie 和 HTTP 认证信息。如果要把 Cookie 发到服务器,一方面要服务器同意,指定 Access-Control-Allow-Credentials 字段。
Access-Control-Allow-Credentials: true
另一方面,开发者必须在 AJAX 请求中打开 withCredentials
属性。
var xhr = new XMLHttpRequest();
xhr.withCredentials = true;
否则,即使服务器同意发送 Cookie,浏览器也不会发送。
需要注意的是,如果要发送Cookie,Access-Control-Allow-Origin
就不能设为星号,必须指定明确的、与请求网页一致的域名。同时,Cookie依然遵循同源政策,只有用服务器域名设置的Cookie才会上传,其他域名的Cookie并不会上传,且(跨源)原网页代码中的document.cookie
也无法读取服务器域名下的Cookie。
3.6 Access-Control-Max-Age
该字段用来指定本次预检请求的有效期,单位为秒。在有效期期间,不用发出另一条预检请求。
语法:
Access-Control-Max-Age: <有效期>
实例:
// 将预检请求的结果缓存10分钟
Access-Control-Max-Age: 600
3.7 Access-Control-Request-Headers
该字段出现于 预检请求中,用于通知服务器在真正的请求中会采用哪些请求头。
语法:
Access-Control-Request-Headers: <header-name>, <header-name>, ...
实例:
Access-Control-Request-Headers: X-PINGOTHER, Content-Type
3.8 Access-Control-Request-Method
该字段出现于预检请求中,用于通知服务器在真正的请求中会采用哪种 HTTP 方法。
语法:
Access-Control-Request-Method: <method>
实例:
Access-Control-Request-Method: POST