JavaScript同源策略和跨域访问实例详解


Posted in Javascript onApril 03, 2018

本文实例讲述了JavaScript同源策略和跨域访问。分享给大家供大家参考,具体如下:

1. 什么是同源策略

理解跨域首先必须要了解同源策略。同源策略是浏览器上为安全性考虑实施的非常重要的安全策略。

何谓同源:

URL由协议、域名、端口和路径组成,如果两个URL的协议、域名和端口相同,则表示他们同源。

同源策略:

浏览器的同源策略,限制了来自不同源的"document"或脚本,对当前"document"读取或设置某些属性。 (白帽子讲web安全[1])

从一个域上加载的脚本不允许访问另外一个域的文档属性。

举个例子:

比如一个恶意网站的页面通过iframe嵌入了银行的登录页面(二者不同源),如果没有同源限制,恶意网页上的javascript脚本就可以在用户登录银行的时候获取用户名和密码。

在浏览器中,<script>、<img>、<iframe>、<link>等标签都可以加载跨域资源,而不受同源限制,但浏览器限制了JavaScript的权限使其不能读、写加载的内容。

另外同源策略只对网页的HTML文档做了限制,对加载的其他静态资源如javascript、css、图片等仍然认为属于同源。

代码示例(http://localhost:8080/和http://localhost:8081由于端口不同而不同源):

http://localhost:8080/test.html

<html>
  <head><title>test same origin policy</title></head>
  <body>
    <iframe id="test" src="http://localhost:8081/test2.html"></iframe>
    <script type="text/javascript">
      document.getElementById("test").contentDocument.body.innerHTML = "write somthing";
    </script>
  </body>
</html>

http://localhost:8081/test2.html

<html>
  <head><title>test same origin policy</title></head>
  <body>
    Testing.
  </body>
</html>

在Firefox中会得到如下错误:

Error: Permission denied to access property 'body'

Document对象的domain属性存放着装载文档的服务器的主机名,可以设置它。

例如来自"blog.3water.com"和来自"bbs.3water.com"的页面,都将document.domain设置为"3water.com",则来自两个子域名的脚本即可相互访问。

出于安全的考虑,不能设置为其他主domain,比如https://3water.com/不能设置为sina.com

2. Ajax跨域

Ajax (XMLHttpRequest)请求受到同源策略的限制。

Ajax通过XMLHttpRequest能够与远程的服务器进行信息交互,另外XMLHttpRequest是一个纯粹的Javascript对象,这样的交互过程,是在后台进行的,用户不易察觉。

因此,XMLHTTP实际上已经突破了原有的Javascript的安全限制。

举个例子:

假设某网站引用了其它站点的javascript,这个站点被compromise并在javascript中加入获取用户输入并通过ajax提交给其他站点,这样就可以源源不断收集信息。

或者某网站因为存在漏洞导致XSS注入了javascript脚本,这个脚本就可以通过ajax获取用户信息并通过ajax提交给其他站点,这样就可以源源不断收集信息。

如果我们又想利用XMLHTTP的无刷新异步交互能力,又不愿意公然突破Javascript的安全策略,可以选择的方案就是给XMLHTTP加上严格的同源限制。

这样的安全策略,很类似于Applet的安全策略。IFrame的限制还仅仅是不能访问跨域HTMLDOM中的数据,而XMLHTTP则根本上限制了跨域请求的提交。(实际上下面提到了CORS已经放宽了限制)

随着Ajax技术和网络服务的发展,对跨域的要求也越来越强烈。下面介绍Ajax的跨域技术。

2.1 JSONP

JSONP技术实际和Ajax没有关系。我们知道<script>标签可以加载跨域的javascript脚本,并且被加载的脚本和当前文档属于同一个域。因此在文档中可以调用/访问脚本中的数据和函数。如果javascript脚本中的数据是动态生成的,那么只要在文档中动态创建<script>标签就可以实现和服务端的数据交互。

JSONP就是利用<script>标签的跨域能力实现跨域数据的访问,请求动态生成的JavaScript脚本同时带一个callback函数名作为参数。其中callback函数本地文档的JavaScript函数,服务器端动态生成的脚本会产生数据,并在代码中以产生的数据为参数调用callback函数。当这段脚本加载到本地文档时,callback函数就被调用。

第一个站点的测试页面(http://localhost:8080/test.html):

<script src="http://localhost:8081/test_data.js">
  <script>
    function test_handler(data) {
      console.log(data);
    }
</script>

服务器端的Javascript脚本(http://localhost:8081/test_data.js):

test_handler('{"data": "something"}');

为了动态实现JSONP请求,可以使用Javascript动态插入<script>标签:

<script type="text/javascript">
    // this shows dynamic script insertion
    var script = document.createElement('script');
    script.setAttribute('src', url);
    // load the script
    document.getElementsByTagName('head')[0].appendChild(script);
</script>

JSONP协议封装了上述步骤,jQuery中统一是现在AJAX中(其中data type为JSONP):

http://localhost:8080/test?callback=test_handler

为了支持JSONP协议,服务器端必须提供特别的支持[2],另外JSONP只支持GET请求。

2.2 Proxy

使用代理方式跨域更加直接,因为SOP的限制是浏览器实现的。如果请求不是从浏览器发起的,就不存在跨域问题了。

使用本方法跨域步骤如下:

1. 把访问其它域的请求替换为本域的请求

2. 本域的请求是服务器端的动态脚本负责转发实际的请求

各种服务器的Reverse Proxy功能都可以非常方便的实现请求的转发,如Apache httpd + mod_proxy。

Eg.

为了通过Ajax从http://localhost:8080访问http://localhost:8081/api,可以将请求发往http://localhost:8080/api。

然后利用Apache Web服务器的Reverse Proxy功能做如下配置:

ProxyPass /api http://localhost:8081/api

2.3 CORS

2.3.1 Cross origin resource sharing

“Cross-origin resource sharing (CORS) is a mechanism that allows a web page to make XMLHttpRequests to another domain. Such "cross-domain" requests would otherwise be forbidden by web browsers, per the same origin security policy. CORS defines a way in which the browser and the server can interact to determine whether or not to allow the cross-origin request. It is more powerful than only allowing same-origin requests, but it is more secure than simply allowing all such cross-origin requests.” ----Wikipedia[3]

通过在HTTP Header中加入扩展字段,服务器在相应网页头部加入字段表示允许访问的domain和HTTP method,客户端检查自己的域是否在允许列表中,决定是否处理响应。

实现的基础是JavaScript不能够操作HTTP Header。某些浏览器插件实际上是具有这个能力的。

服务器端在HTTP的响应头中加入(页面层次的控制模式):

Access-Control-Allow-Origin: example.com
Access-Control-Request-Method: GET, POST
Access-Control-Allow-Headers: Content-Type, Authorization, Accept, Range, Origin
Access-Control-Expose-Headers: Content-Range
Access-Control-Max-Age: 3600

多个域名之间用逗号分隔,表示对所示域名提供跨域访问权限。"*"表示允许所有域名的跨域访问。

客户端可以有两种行为:

1. 发送OPTIONS请求,请求Access-Control信息。如果自己的域名在允许的访问列表中,则发送真正的请求,否则放弃请求发送。

2. 直接发送请求,然后检查response的Access-Control信息,如果自己的域名在允许的访问列表中,则读取response body,否则放弃。

本质上服务端的response内容已经到达本地,JavaScript决定是否要去读取。

Support: [Javascript Web Applications]
* IE >= 8 (需要安装caveat)
* Firefox >= 3
* Safari 完全支持
* Chrome 完全支持
* Opera 不支持

 2.3.2 测试

测试页面http://localhost:8080/test3.html使用jquery发送Ajax请求。

<html>
    <head><title>testing cross sop</title></head>
    <body>
      Testing.
      <script src="jquery-2.0.0.min.js"></script>
      <script type='text/javascript'>
        $.ajax({
          url: 'http://localhost:8000/hello',
          success: function(data) {
            alert(data);
          },
          error: function() {
            alert('error');
          }
        });
      </script>
    </body>
</html>

测试Restful API(http://localhost:8000/hello/{name})使用bottle.py来host。

from bottle import route, run, response
@route('/hello')
def index():
  return 'Hello World.'
run(host='localhost', port=8000)

测试1:

测试正常的跨域请求的行为。

测试结果:

1. 跨域GET请求已经发出,请求header中带有

    Origin    http://localhost:8080

2. 服务器端正确给出response

3. Javascript拒绝读取数据,在firebug中发现reponse为空,并且触发error回调

测试2:

测试支持CORS的服务器的跨域请求行为。

对Restful API做如下改动,在response中加入header:

def index():
  #Add CORS header#
  response.set_header("Access-Control-Allow-Origin", "http://localhost:8080")
  return 'Hello World.'

测试结果:

1. 跨域GET请求已经发出,请求header中带有

Origin    http://localhost:8080

2. 服务器端正确给出response

3. 客户端正常获取数据

测试3:

测试OPTIONS请求获取CORS信息。
对客户端的Ajax请求增加header:

$.ajax({
 url: 'http://localhost:8000/hello',
 headers: {'Content-Type': 'text/html'},
 success: function(data) {
   alert(data);
 },
 error: function() {
   alert('error');
 }
});

对Restful API做如下改动:

@route('/hello', method = ['OPTIONS', 'GET'])
def index():
  if request.method == 'OPTIONS':
    return ''
  return 'Hello World.'

测试结果:

1. Ajax函数会首先发送OPTIONS请求
2. 针对OPTIONS请求服务器
3. 客户端发现没有CORS header后不会发送GET请求

测试4:

增加服务器端对OPTIONS方法的处理。

对Restful API做如下改动:

@route('/hello', method = ['OPTIONS', 'GET'])
def index():
    response.headers['Access-Control-Allow-Origin'] = 'http://localhost:8080'
    response.headers['Access-Control-Allow-Methods'] = 'GET, OPTIONS'
    response.headers['Access-Control-Allow-Headers'] = 'Origin, Accept, Content-Type'
    if request.method == 'OPTIONS':
      return ''
    return 'Hello World.'

测试结果:

1. Ajax函数会首先发送OPTIONS请求
2. 针对OPTIONS请求服务器
3. 客户端匹配CORS header中的allow headers and orgin后会正确发送GET请求并获取结果

测试发现,Access-Control-Allow-Headers是必须的。

CORS协议提升了Ajax的跨域能力,但也增加了风险。一旦网站被注入脚本或XSS攻击,将非常方便的获取用户信息并悄悄传递出去。

4. Cookie 同源策略

Cookie中的同源只关注域名,忽略协议和端口。所以https://localhost:8080/和http://localhost:8081/的Cookie是共享的。

5. Flash/SilverLight跨域

浏览器的各种插件也存在跨域需求。通常是通过在服务器配置crossdomain.xml[4],设置本服务允许哪些域名的跨域访问。

客户端会首先请求此文件,如果发现自己的域名在访问列表里,就发起真正的请求,否则不发送请求。

<?xml version="1.0"?>
  <!DOCTYPE cross-domain-policy SYSTEM "http://www.macromedia.com/xml/dtds/cross-domain-policy.dtd">
  <cross-domain-policy>
  <allow-access-from domain="*"/>
  <allow-http-request-headers-from domain="*" headers="*"/>
</cross-domain-policy>

通常crossdomain.xml放置在网站根目录。

6. 总结

互联网的发展催生了跨域访问的需求,各种跨域方法和协议满足了需求但也增加了各种风险。尤其是XSS和CSRF等攻击的盛行也得益于此。

了解这些技术背景有助于在实际项目中熟练应用并规避各种安全风险。

Reference

[1] 白帽子讲Web安全: https://3water.com/books/164067.html
[2] 使用 JSONP 实现跨域通信: http://www.ibm.com/developerworks/cn/web/wa-aj-jsonp1/
[3] Cross-origin resource sharing: http://en.wikipedia.org/wiki/Cross-Origin_Resource_Sharing
[4] Cross-domain policy for Flash movies: http://kb2.adobe.com/cps/142/tn_14213.html

更多关于JavaScript相关内容可查看本站专题:《javascript面向对象入门教程》、《JavaScript切换特效与技巧总结》、《JavaScript查找算法技巧总结》、《JavaScript错误与调试技巧总结》、《JavaScript数据结构与算法技巧总结》、《JavaScript遍历算法与技巧总结》及《JavaScript数学运算用法总结》

希望本文所述对大家JavaScript程序设计有所帮助。

Javascript 相关文章推荐
jquery控制listbox中项的移动并排序
Nov 12 Javascript
Jquery Validate 正则表达式实用验证代码大全
Aug 23 Javascript
Javascript:为input设置readOnly属性(示例讲解)
Dec 25 Javascript
JS将光标聚焦在文本最后的实现代码
Mar 28 Javascript
JS传值出现中文参数乱码的解决方法
Jun 30 Javascript
JS生成不重复的随机数组的简单实例
Jul 10 Javascript
利用JQuery直接调用asp.net后台的简单方法
Oct 27 Javascript
EasyUI学习之Combobox级联下拉列表(2)
Dec 29 Javascript
JavaScript实现三级联动菜单实例代码
Jun 26 Javascript
AngularJS遍历获取数组元素的方法示例
Nov 11 Javascript
webpack css加载和图片加载的方法示例
Sep 11 Javascript
微信小程序 setData 对 data数据影响问题
Apr 18 Javascript
vue 组件中slot插口的具体用法
Apr 03 #Javascript
react 实现页面代码分割、按需加载的方法
Apr 03 #Javascript
深入浅析Vue中的slots/scoped slots
Apr 03 #Javascript
使用FileReader API创建Vue文件阅读器组件
Apr 03 #Javascript
vue内置指令详解
Apr 03 #Javascript
详解在React里使用&quot;Vuex&quot;
Apr 02 #Javascript
Angular2进阶之如何避免Dom误区
Apr 02 #Javascript
You might like
php cookie的操作实现代码(登录)
2010/12/29 PHP
基于PHP生成静态页的实现方法
2013/05/10 PHP
eclipse php wamp配置教程
2016/06/30 PHP
php处理单文件、多文件上传代码分享
2016/08/24 PHP
JAVASCRIPT车架号识别/验证函数代码 汽车车架号验证程序
2012/01/08 Javascript
Javascript中找到子元素在父元素内相对位置的代码
2012/07/21 Javascript
在jQuery ajax中按钮button和submit的区别分析
2012/10/07 Javascript
解决IE6的PNG透明JS插件使用介绍
2013/04/17 Javascript
跟我学习javascript的prototype,getPrototypeOf和__proto__
2015/11/17 Javascript
Node.js 条形码识别程序构建思路详解
2016/02/14 Javascript
Javascript闭包与函数柯里化浅析
2016/06/22 Javascript
AngularJS 路由详解和简单实例
2016/07/28 Javascript
JS图片放大效果简单实现代码
2016/09/08 Javascript
React-router中结合webpack实现按需加载实例
2017/05/25 Javascript
template.js前端模板引擎使用详解
2017/10/10 Javascript
vue如何通过id从列表页跳转到对应的详情页
2018/05/01 Javascript
vue单页开发父子组件传值思路详解
2018/05/18 Javascript
vue用递归组件写树形控件的实例代码
2018/07/19 Javascript
Vue 引入AMap高德地图的实现代码
2019/04/29 Javascript
vue实现把接口单独存放在一个文件方式
2020/08/13 Javascript
[01:38]完美世界高校联赛决赛花絮
2018/12/02 DOTA
Python列表list操作符实例分析【标准类型操作符、切片、连接字符、列表解析、重复操作等】
2017/07/24 Python
解决python使用open打开文件中文乱码的问题
2017/12/29 Python
Python利用ORM控制MongoDB(MongoEngine)的步骤全纪录
2018/09/13 Python
Numpy 中的矩阵求逆实例
2019/08/26 Python
三个python爬虫项目实例代码
2019/12/28 Python
Python读写操作csv和excle文件代码实例
2020/03/16 Python
实例讲解CSS3中的border-radius属性
2015/08/18 HTML / CSS
详解CSS3的box-shadow属性制作边框阴影效果的方法
2016/05/10 HTML / CSS
HTML5 Canvas玩转酷炫大波浪进度图效果实例(附demo)
2016/12/14 HTML / CSS
一家外企的面试题目(C/C++面试题,C语言面试题)
2014/03/24 面试题
设计部经理的岗位职责
2013/11/16 职场文书
优秀毕业生自我鉴定
2014/01/19 职场文书
施工单位工程部经理岗位职责
2015/04/09 职场文书
2016年七夕情人节宣传语
2015/11/25 职场文书
2016年法制宣传月活动总结
2016/04/01 职场文书