什么是浏览器同源策略
同源策略 (Same origin policy) 是一种约定, 它是浏览器最核心也最基本的安全功能, 如果缺少了同源策略, 则浏览器的正常功能可能都会受到影响. 可以说 web 是构建在同源策略基础之上的, 浏览器只是针对同源策略的一种实现.
它的核心就在于它认为自任何站点装载的信赖内容是不安全的. 当被浏览器半信半疑的脚本运行在沙箱时, 它们应该只被允许访问来自同一站点的资源, 而不是那些来自其它站点可能怀有恶意的资源.
所谓同源是指: 域名, 协议, 端口相同.
下表是相对于
http://www.laixiangran.cn/home/index.html
的同源检测结果:
另外, 同源策略又分为以下两种:
DOM 同源策略: 禁止对不同源页面 DOM 进行操作. 这里主要场景是 iframe 跨域的情况, 不同域名的 iframe 是限制互相访问的.
XMLHttpRequest 同源策略: 禁止使用 XHR 对象向不同源的服务器地址发起 HTTP 请求.
为什么要有跨域限制
因为存在浏览器同源策略, 所以才会有跨域问题. 那么浏览器是出于何种原因会有跨域的限制呢. 其实不难想到, 跨域限制主要的目的就是为了用户的上网安全.
如果浏览器没有同源策略, 会存在什么样的安全问题呢. 下面从 DOM 同源策略和 XMLHttpRequest 同源策略来举例说明:
如果没有 DOM 同源策略, 也就是说不同域的 iframe 之间可以相互访问, 那么黑客可以这样进行攻击:
做一个假网站, 里面用 iframe 嵌套一个银行网站 http://mybank.com.
把 iframe 宽高啥的调整到页面全部, 这样用户进来除了域名, 别的部分和银行的网站没有任何差别.
这时如果用户输入账号密码, 我们的主网站可以跨域访问到 http://mybank.com 的 dom 节点, 就可以拿到用户的账户密码了.
如果 XMLHttpRequest 同源策略, 那么黑客可以进行 CSRF(跨站请求伪造) 攻击:
用户登录了自己的银行页面 http://mybank.com,http://mybank.com 向用户的 cookie 中添加用户标识.
用户浏览了恶意页面 http://evil.com, 执行了页面中的恶意 AJAX 请求代码.
http://evil.com 向 http://mybank.com 发起 AJAX HTTP 请求, 请求会默认把 http://mybank.com 对应 cookie 也同时发送过去.
银行页面从发送的 cookie 中提取用户标识, 验证用户无误, response 中返回请求数据. 此时数据就泄露了.
而且由于 Ajax 在后台执行, 用户无法感知这一过程.
因此, 有了浏览器同源策略, 我们才能更安全的上网.
跨域的解决方法
从上面我们了解到了浏览器同源策略的作用, 也正是有了跨域限制, 才使我们能安全的上网. 但是在实际中, 有时候我们需要突破这样的限制, 因此下面将介绍几种跨域的解决方法.
CORS(跨域资源共享)
CORS(Cross-origin resource sharing, 跨域资源共享)是一个 W3C 标准, 定义了在必须访问跨域资源时, 浏览器与服务器应该如何沟通. CORS 背后的基本思想, 就是使用自定义的 HTTP 头部让浏览器与服务器进行沟通, 从而决定请求或响应是应该成功, 还是应该失败.
CORS 需要浏览器和服务器同时支持. 目前, 所有浏览器都支持该功能, IE 浏览器不能低于 IE10.
整个 CORS 通信过程, 都是浏览器自动完成, 不需要用户参与. 对于开发者来说, CORS 通信与同源的 AJAX 通信没有差别, 代码完全一样. 浏览器一旦发现 AJAX 请求跨源, 就会自动添加一些附加的头信息, 有时还会多出一次附加的请求, 但用户不会有感觉.
因此, 实现 CORS 通信的关键是服务器. 只要服务器实现了 CORS 接口, 就可以跨源通信.
浏览器将 CORS 请求分成两类: 简单请求 (simple request) 和非简单请求(not-so-simple request).
只要同时满足以下两大条件, 就属于简单请求.
请求方法是以下三种方法之一:
HEAD
GET
POST
HTTP 的头信息不超出以下几种字段:
- Accept
- Accept-Language
- Content-Language
- Last-Event-ID
Content-Type: 只限于三个值 application/x-www-form-urlencoded,multipart/form-data,text/plain
凡是不同时满足上面两个条件, 就属于非简单请求.
浏览器对这两种请求的处理, 是不一样的.
简单请求
在请求中需要附加一个额外的 Origin 头部, 其中包含请求页面的源信息(协议, 域名和端口), 以便服务器根据这个头部信息来决定是否给予响应. 例如:
Origin: http://www.laixiangran.cn
如果服务器认为这个请求可以接受, 就在 Access-Control-Allow-Origin 头部中回发相同的源信息(如果是公共资源, 可以回发 * ). 例如:
Access-Control-Allow-Origin:http://www.laixiangran.cn
没有这个头部或者有这个头部但源信息不匹配, 浏览器就会驳回请求. 正常情况下, 浏览器会处理请求. 注意, 请求和响应都不包含 cookie 信息.
如果需要包含 cookie 信息, ajax 请求需要设置 xhr 的属性 withCredentials 为 true, 服务器需要设置响应头部
- Access-Control-Allow-Credentials: true
- .
非简单请求
浏览器在发送真正的请求之前, 会先发送一个 Preflight 请求给服务器, 这种请求使用 OPTIONS 方法, 发送下列头部:
Origin: 与简单的请求相同.
Access-Control-Request-Method: 请求自身使用的方法.
Access-Control-Request-Headers: (可选)自定义的头部信息, 多个头部以逗号分隔.
例如:
- Origin: http://www.laixiangran.cn
- Access-Control-Request-Method: POST
- Access-Control-Request-Headers: NCZ
发送这个请求后, 服务器可以决定是否允许这种类型的请求. 服务器通过在响应中发送如下头部与浏览器进行沟通:
Access-Control-Allow-Origin: 与简单的请求相同.
Access-Control-Allow-Methods: 允许的方法, 多个方法以逗号分隔.
Access-Control-Allow-Headers: 允许的头部, 多个方法以逗号分隔.
Access-Control-Max-Age: 应该将这个 Preflight 请求缓存多长时间(以秒表示).
例如:
- Access-Control-Allow-Origin: http://www.laixiangran.cn
- Access-Control-Allow-Methods: GET, POST
- Access-Control-Allow-Headers: NCZ
- Access-Control-Max-Age: 1728000
- // 1. 定义一个 回调函数 handleResponse 用来接收返回的数据
- function handleResponse(data) {
- console.log(data);
- };
- // 2. 动态创建一个 script 标签, 并且告诉后端回调函数名叫 handleResponse
- var body = document.getElementsByTagName('body')[0];
- var script = document.gerElement('script');
- script.src = 'http://www.laixiangran.cn/json?callback=handleResponse';
- body.appendChild(script);
- // 3. 通过 script.src 请求 `http://www.laixiangran.cn/json?callback=handleResponse`,
- // 4. 后端能够识别这样的 URL 格式并处理该请求, 然后返回 handleResponse({"name": "laixiangran"}) 给浏览器
- // 5. 浏览器在接收到 handleResponse({"name": "laixiangran"}) 之后立即执行 , 也就是执行 handleResponse 方法, 获得后端返回的数据, 这样就完成一次跨域请求了.
- var img = new Image();
- // 通过 onload 及 onerror 事件可以知道响应是什么时候接收到的, 但是不能获取响应文本
- img.onload = img.onerror = function() {
- console.log("Done!");
- }
- // 请求数据通过查询字符串形式发送
- img.src = 'http://www.laixiangran.cn/test?name=laixiangran';
- <iframe src="http://laixiangran.cn/b.html" id="myIframe" onload="test()">
- <script>
- document.domain = 'laixiangran.cn'; // 设置成主域
- function test() {
- console.log(document.getElementById('myIframe').contentWindow);
- }
- </script>
- <iframe src="http://laixiangran.cn/b.html" id="myIframe" onload="test()" style="display: none;">
- <script>
- // 2. iframe 载入 "http://laixiangran.cn/b.html 页面后会执行该函数
- function test() {
- var iframe = document.getElementById('myIframe');
- // 重置 iframe 的 onload 事件程序,
- // 此时经过后面代码重置 src 之后,
- // http://www.laixiangran.cn/a.html 页面与该 iframe 在同一个源了, 可以相互访问了
- iframe.onload = function() {
- var data = iframe.contentWindow.name; // 4. 获取 iframe 里的 window.name
- console.log(data); // hello world!
- };
- // 3. 重置一个与 http://www.laixiangran.cn/a.html 页面同源的页面
- iframe.src = 'http://www.laixiangran.cn/c.html';
- }
- </script>
- <script type="text/javascript">
- // 1. 给当前的 window.name 设置一个 http://www.laixiangran.cn/a.html 页面想要得到的数据值
- window.name = "hello world!";
- </script>
- <iframe src="http://laixiangran.cn/b.html" id="myIframe" onload="test()" style="display: none;">
- <script>
- // 2. iframe 载入 "http://laixiangran.cn/b.html 页面后会执行该函数
- function test() {
- // 3. 获取通过 http://laixiangran.cn/b.html 页面设置 hash 值
- var data = window.location.hash;
- console.log(data);
- }
- </script>
- <script type="text/javascript">
- // 1. 设置父页面的 hash 值
- parent.location.hash = "world";
- </script>
- <iframe src="http://laixiangran.cn/b.html" id="myIframe" onload="test()" style="display: none;">
- <script>
- // 1. iframe 载入 "http://laixiangran.cn/b.html 页面后会执行该函数
- function test() {
- // 2. 获取 http://laixiangran.cn/b.html 页面的 window 对象,
- // 然后通过 postMessage 向 http://laixiangran.cn/b.html 页面发送消息
- var iframe = document.getElementById('myIframe');
- var win = iframe.contentWindow;
- win.postMessage('我是来自 http://www.laixiangran.cn/a.html 页面的消息', '*');
- }
- </script>
- <script type="text/javascript">
- // 注册 message 事件用来接收消息
- window.onmessage = function(e) {
- e = e || event; // 获取事件对象
- console.log(e.data); // 通过 data 属性得到发送来的消息
- }
- </script>
来源: https://www.cnblogs.com/laixiangran/p/9064769.html