Web 跨域

作者: KeKeMars | 来源:发表于2015-11-09 14:49 被阅读339次

为什么需要跨域?

浏览器 同源策略 (域名、协议、端口相同)限制其他网页对本网页进行非法篡改, 只有带有 src 属性的标签才允许跨域(<img> <iframe> <script>)

JSONP

JSONP(JSON with Padding)是一个非官方的协议,它允许在服务器端集成Script tags返回至客户端,通过javascript callback的形式实现跨域访问(这仅仅是JSONP简单的实现形式)。利用<script/>标签对javascript进行动态解析来实现(其实也可以用eval函数),然后在服务端输出JSON数据并执行回调函数,从而解决了跨域的数据请求。

IE 兼容

P3P 规范让 IE 跨域接受第三方 cookie

header('P3P:CP="IDC DSP COR ADM DEVi TAIi PSA PSD IVAi IVDi CONi HIS OUR IND CNT"');

原理

1.首先在客户端注册一个callback, 然后把callback的名字传给服务器。

  1. 此时,服务器先生成 json 数据。
  2. 然后以 javascript 语法的方式,生成一个function , function 名字就是传递上来的参数 jsonp.
  3. 最后将 json 数据直接以入参的方式,放置到 function 中,这样就生成了一段 js 语法的文档,返回给客户端。
  4. 客户端浏览器,解析script标签,并执行返回的 javascript 文档,此时数据作为参数,传入到了客户端预先定义好的 callback 函数里.(动态执行回调函数)

缺点

  • 只能采用GET请求,数据量大的跨域请求不能使用
  • 如果使用eval来解析会容易出现安全问题
  • 没有JSONP调用的错误处理,如果无效静默失败
  • 被不信任的服务使用时会很危险,因为 JSONP 服务返回打包在函数调用中的 JSON 响应,而函数调用是由浏览器执行的,这使宿主 Web 应用程序更容易受到各类攻击。

代码示例

原生实现JSONP跨域
// 客户端
<script>
    // 这是回调方法
    function cb(data){
        alert(data.website);
    }
</script>
<!--这是跨域请求的代码,切记,这段代码要在回调函数之后-->
<script src="http://172.22.22.120/new/ajax_jsonp.php?callback=cb"></script>
<!-- 服务端 -->
<?php
    $cb = htmlspecialchars($_GET['callback']);  // 注意了,这里要做好过滤,防止xss攻击
    echo $cb,'(',json_encode(array('website'=>'hcoding')),')';  // 返回客户端的数据为:cb({"name":"hcoding"})  这是一段js代码
?>
AJax实现JSONP

客户端代码:

<script>
    // 客户端使用getJSON方法请求另一台机子上的脚本
    // 浏览器会生成一个随机的callback参数
    $.getJSON("http://172.22.22.120/new/ajax_jsonp.php?callback=?",function(json){
        alert(json.website);
    });
// $.getJSON简单易用,但就是不能指定回调函数。
</script>
//或者
<script>
    $.ajax({
        type : "GET",
        url : "http://172.22.22.120/new/ajax_jsonp.php",
        dataType : "jsonp",    // 数据格式指定为jsonp
        jsonp: "callback",     // 服务点通过这个键值获取回调方法
        jsonpCallback:"cb",   // 指定回调方法
        success : function(json){

        },
    });   

    // 回调方法
    function cb(data){
        alert(data.website);
    }
</script>

服务端PHP脚本代码:

<?php
    $cb = htmlspecialchars($_GET['callback']);  // 注意了,这里要做好过滤,防止xss攻击
    echo $cb,'(',json_encode(array('website'=>'hcoding')),')';  // 返回客户端的数据,这是一段js代码
?>

CORS (IE8+) 推荐使用

跨源资源共享标准( cross-origin sharing standard ) 使得以下场景可以使用跨站 HTTP 请求:

  • 如上所述,使用 XMLHttpRequest 发起跨站 HTTP 请求。
  • Web 字体 (CSS 中通过 @font-face 使用跨站字体资源), 因此,网站就可以发布 TrueType 字体资源,并只允许已授权网站进行跨站调用。
  • WebGL 贴图
  • 使用 drawImage API 在 canvas 上画图

相关Header

服务端设置Header: (ResponseHeader)

  • Access-Control-Allow-Origin * 服务器设置允许访问的源的网址
  • * 可以为网站网址 如http://api.a.com
  • Access-Control-Expose-Headers: X-My-Custom-Header, X-Another-Custom-Header 允许访问服务器的头信息
  • Access-Control-Max-Age: <delta-seconds> 允许预请求参数缓存的毫秒数,在此期间不用发出另一条预请求
  • Access-Control-Allow-Credentials: true | false
  • Access-Control-Allow-Methods: <method>[, <method>]*
  • Access-Control-Allow-Headers: X-PINGOTHER 这样在实际的请求里请求头信息里就可以有这么一条:X-PINGOTHER: pingpong可以有多个自定义HTTP请求头,用逗号分隔.
  • Access-Control-Allow-Headers: <field-name>[, <field-name>]*

HTTP 请求头(RequestHeader)

注意这些请求头信息都是在请求服务器的时候已经为你设置好的,当开发者使用跨域的XMLHttpRequest的时候,不需要手动的设置这些头信息.

  • Origin: <origin>注意,不仅仅是跨域请求,普通请求也会带有ORIGIN头信息.
  • Access-Control-Request-Method: <method>
  • Access-Control-Request-Headers
  • Access-Control-Request-Headers: <field-name>[, <field-name>]*

请求方式

  • 简单请求
  • 只使用 GET, HEAD 或者 POST 请求方法。如果使用 POST 向服务器端传送数据,则数据类型(Content-Type)只能是 application/x-www-form-urlencoded, multipart/form-data 或 text/plain中的一种。
  • 不会使用自定义请求头(类似于 X-Modified 这种)。
  • 预请求
  • 请求以 GET, HEAD 或者 POST 以外的方法发起请求。或者,使用 POST,但请求数据为 application/x-www-form-urlencoded, multipart/form-data 或者 text/plain 以外的数据类型。比如说,用 POST 发送数据类型为 application/xml 或者 text/xml 的 XML 数据的请求。
  • 使用自定义请求头(比如添加诸如 X-PINGOTHER)
  • 附带凭证信息的请求
  • XMLHttpRequest和访问控制功能,最有趣的特性就是,发送凭证请求(HTTP Cookies和验证信息)的功能。一般而言,对于跨站请求,浏览器是不会发送凭证信息的。但如果将XMLHttpRequest的一个特殊标志位设置为true,浏览器就将允许该请求的发送。

安全问题:不要依赖CORS当中的权限制度,应当使用更多其它的措施来保障,比如OAuth2

iframe 隐藏表单,进行POST提交(非现代浏览器)

  • 建立一个iframe,iframe内的JS创建一个form表单,并可以将接收到的参数放入表单中POST提交
  • 将iframe页面插入到页面中。

window.name

有三个页面:

  • a.com/app.html:应用页面。
  • a.com/proxy.html:代理文件,一般是一个没有任何内容的html文件,需要和应用页面在同一域下。
  • b.com/data.html:应用页面需要获取数据的页面,可称为数据页面。

实现起来基本步骤如下:

  • 在应用页面(a.com/app.html)中创建一个iframe,把其src指向数据页面(b.com/data.html)。
    数据页面会把数据附加到这个iframe的window.name上,data.html代码如下:
<script type="text/javascript">
    window.name = 'I was there!';    // 这里是要传输的数据,大小一般为2M,IE和firefox下可以大至32M左右
                                     // 数据格式可以自定义,如json、字符串
</script>
  • 在应用页面(a.com/app.html)中监听iframe的onload事件,在此事件中设置这个iframe的src指向本地域的代理文件(代理文件和应用页面在同一域下,所以可以相互通信)。app.html部分代码如下:
<script type="text/javascript">
    var state = 0, 
    iframe = document.createElement('iframe'),
    loadfn = function() {
        if (state === 1) {
            var data = iframe.contentWindow.name;    // 读取数据
            alert(data);    //弹出'I was there!'
        } else if (state === 0) {
            state = 1;
            iframe.contentWindow.location = "http://a.com/proxy.html";    // 设置的代理文件
        }  
    };
    iframe.src = 'http://b.com/data.html';
    if (iframe.attachEvent) {
        iframe.attachEvent('onload', loadfn);
    } else {
        iframe.onload  = loadfn;
    }
    document.body.appendChild(iframe);
</script>
  • 获取数据以后销毁这个iframe,释放内存;这也保证了安全(不被其他域frame js访问)。
<script type="text/javascript">
    iframe.contentWindow.document.write('');
    iframe.contentWindow.close();
    document.body.removeChild(iframe);
</script>

总结起来即:iframe的src属性由外域转向本地域,跨域数据即由iframe的window.name从外域传递到本地域。这个就巧妙地绕过了浏览器的跨域访问限制,但同时它又是安全操作。

HTML5 postMessage(IE8+)

otherWindow.postMessage(message, targetOrigin);

  • otherWindow: 对接收信息页面的window的引用。可以是页面中iframe的contentWindow属性;window.open的返回值;通过name或下标从window.frames取到的值。
  • message: 所要发送的数据,string类型。
  • targetOrigin: 用于限制otherWindow,“*”表示不作限制

a.com/index.html中的代码:

<iframe id="ifr" src="b.com/index.html"></iframe>
<script type="text/javascript">
window.onload = function() {
    var ifr = document.getElementById('ifr');
    var targetOrigin = 'http://b.com';  // 若写成'http://b.com/c/proxy.html'效果一样
                                        // 若写成'http://c.com'就不会执行postMessage了
    ifr.contentWindow.postMessage('I was there!', targetOrigin);
};
</script>

b.com/index.html中的代码:

<script type="text/javascript">
    window.addEventListener('message', function(event){
        // 通过origin属性判断消息来源地址
        if (event.origin == 'http://a.com') {
            alert(event.data);    // 弹出"I was there!"
            alert(event.source);  // 对a.com、index.html中window对象的引用
                                  // 但由于同源策略,这里event.source不可以访问window对象
        }
    }, false);
</script>

用nginx/Apache把B网站的数据url反向代理。(数据转发)

参考

相关文章

  • go web 请求跨域

    go web 请求跨域

  • 跨域

    什么是跨域 跨域指的是浏览器源文档访问另一个源文档。具体可以查看web同源策略 为什么要有跨域 跨域是出于浏览区安...

  • Ajax跨域设置Access-Control-Allow-Ori

    方法一:部分跨域 在需要跨域的Controller上添加注解 方法二:全局跨域 1、新建类 2、在web.xml添加标签

  • 跨域 && 监听对象改变

    跨域 引起跨域的原因:协议、域名、端口只要有一个不同都会导致跨域 解决办法: jsonp 通常为了减轻web服务器...

  • Web 跨域

    为什么需要跨域? 浏览器 同源策略 (域名、协议、端口相同)限制其他网页对本网页进行非法篡改, 只有带有 src ...

  • Spring Boot使用CORS解决跨域问题

    一、跨域问题描述 Web开发经常会遇到跨域问题,解决方案有:jsonp,iframe,CORS等等。CORS 与 ...

  • 一个iOS程序员眼中的跨域问题

    摘要: 跨域问题是web开发领域一个常见的问题,相信每个web开发者都遇到“跨域”的问题最近公司的iOS开发任务比...

  • 跨域

    --args --disable-web-security --user-data-dir 软件跨域

  • web前端安全之XSS攻击

    XSS(cross-site scripting跨域脚本攻击)攻击是最常见的Web攻击,其重点是“跨域”和“客户端...

  • jwt介绍,以及在node和koa中的应用

    JWT JSON Web Token( JWT)是目前最流行的跨域认证解决方案。 一般的跨域认证方式 流程如下: ...

网友评论

  • b1225f9473bc:半夜被蚊子叮醒睡不着跑来看简书...略过一遍 感觉很牛逼

本文标题:Web 跨域

本文链接:https://www.haomeiwen.com/subject/sxfhhttx.html