前端跨域问题 以及解决方法

1,431 阅读1分钟

前言:跨域问题在工作中也是经常遇见的,今天想起来就自己写个文章记录一下,算是对自己零散的知识点做一个整理归纳吧。

一.浏览器同源策略

提到跨域就不得不提到浏览器的同源策略,同源策略时浏览器最核心也是最基本的安全功能,是指我们在发送网络请求时请求必须要遵守同源策略,即域名、协议、端口号都要相同。为了保证用户信息安全,防止恶意的网站窃取数据。

示例

http://www.example.com/dir2/other.html:同源
http://example.com/dir/other.html:不同源(域名不同)
http://v2.www.example.com/dir/other.html:不同源(域名不同)
http://www.example.com:81/dir/other.html:不同源(端口不同)

这只是对浏览器同源策略的简单的概括,详细的请看浏览器同源政策及规避方法

二.解决方法

一.jsonp解决跨域

原理:动态添加一个 script 标签。而script 标签的src属性是没有跨域限制的。
1.原生实现
实现代码:
<script>
    var script = document.creatElement('script');
    script.type = 'text/javascript';
    script.src = 'http://xxx.com?callBack=callBack';
    document.head.appendChild(script);
    function callBack(res){
        alert(res)
    }
</script>
2.jquery ajax:jq实现
$.ajax({
    url:'http://xxx.com',
    type:'get',
    dataType:'jsonp',
    jsonpCallback:'callBack',
    data:{}
})

JSONP 跨域方式的优缺点分析
JSONP有点分析:它不像XMLHttpRequest对象实现的Ajax请求那样受到同源策略的限制;它的兼容性更好,在更加古老的浏览器中都可以运行,不需要XMLHttpRequest或ActiveX的支持;并且在请求完成后可以通过callback的方式回传结果。
JSONP缺点分析:只支持GET请求而不支持POST等其它类型的HTTP请求;它只支持跨域HTTP请求这种情况,不能解决不同域的两个页面之间如何进行JavaScript调用的问题。

二.document.domain + iframe跨域(仅限主域相同,子域不同的跨域场景应用)
两个页面都通过js强制设置documnet.domain为基础域,就实现了同域。
(1)父窗口:(http://www.domain.com/a.html)
<iframe id='iframe' src='http://child.domain.com/b.html'></iframe>
<scripr>
    document.domain = 'domain.com';
    var res = 'data'
</scripr>
(2)子窗口:(http://child.domain.com/b.html)
<script>
    document.domain = 'domain.com';
    //获取父窗口中变量
    console.log('res'+window.parent.res)
</script>
三.location.hash + iframe
因为父窗口可以对iframe进行URL读写,iframe也可以读写父窗口的URL,URL有一部分被称为hash,即#号及其后面的字符,一般用于浏览器的锚点定位,Server端并不关系这部分,HTTP请求过程中不会携带hash,所以这部分修改不会产生HTTP请求,但是会产生浏览器历史记录。此方法的原理就是改变URL的hash部分来进行双向通信。每个window通过改变其他window的location来发送消息(由于两个页面不在同一个域下IE,Chrome 不允许修改parent.location.hash的值,所以要借助于父窗口域名下的一个代理iframe),并通过监听自己的URL变化来接受消息。这个方式的通信会造成一些不必要的浏览器历史记录,而且有些浏览器不支持onhashChange事件,需要轮询来获取URL的改变,最后这样做也存在缺点,诸如数据直接暴露在url中了,数据容量和类型都有限,等等。 列:
假如父页面是baidu.com/a.html,iframe嵌入的页面为google.com/b.html(此处省略了域名等url属性),要实现此两个页面的通信可以通过以下方法。
一、a.html传送数据到b.html
    1.a.html下修改iframe的src为goodle.com/b.html#paco
    2.b.html监听到url发生变化,触发相应操作
二、b.html传送数据到a.html,由于两个页面不在同一个域下IE、Chrome不允许修改parent.location.hash的值   所以要借助于父窗口域名下的一个代理iframe
    1.b.html下创建一个隐藏的iframe,此iframe的src是baidu.com域下的,并挂上要传送的hash数据,如src='www.baidu.com/proxy.html#...'\
    2.proxy.html监听到url发生变化,修改a.html的url(因为a.html和proxy.html同域,所以proxy.html可修改a.html的url hash)
    3.a.html监听到url发生变化,触发相应变化
    
    b.html页面关键代码如下:
    
    try{
       parent.location.hash = 'data';
    }catch(e){
        var ifrpoxy = document.createElement('iframe');
        ifrpoxy.style.display = 'none';
        ifrpoxy.src = 'http://www.baidu.com/proxy.html#date'
    };
    
    //proxy.html页面的关键代码如下:
    //因为parent.parent(即baidu.com/a.html)和baidu.com/proxy.html属于同一个域,所以可以改变其location.hash的值。
    parent.parent.location.hash = self.location.hash.sybstring(1)
四、通过HTML5的postMessage方法实现跨域
postMessage是HTML5中的API,且是为数不多可以跨域操作的windows属性之一,可以用于解决以下问题
  • (1)页面和其它打开的新窗口的数据传递。
  • (2)多窗口之间的消息传递。
  • (3)页面与嵌套的iframe消息传递。
  • (4)上面三个场景的跨域数据传递。
用法:postMessage(data,origin)方法接受两个参数
data:html5规范支持任意基本类型或可复制对象,但部分浏览器只支持字符串,所以传参时最好用JSON.stringify()序列化。
origin:协议+主机+端口号,也可以设置为*,表示可以传递给任意窗口,如果要指定和当前窗口同源的话设置为'/'
//1) a.html:(http://www.domain1.com/a.html) 
<iframe id='iframe' src='http://www.domain2.com/b.html' style='display:none'></iframe>
<script>
    var iframe = document.getElementById('iframe');
    iframe.onload = function(){
        var data = {
            name:"aym"
        };
        //向domain2传送跨域数据
        iframe.contentWindow.postMessage(JSON.stringify(data),'http://www.domain2.com')
    };
    //接受domain2返回数据
    window.addEventListener('message',function(e){
        alert(e.data)
    },false)
</script>
//2)b.html:(http://www.domain2.com/b.html)

<script>
    //接受domain1的数据
    window.addEventListener('message',function(e){
        alert(e.data);
        var data = JSON.parse(e.data);
        if(data){
            data.age = 18;
            //处理返回domain1.
            window.parent.postMessage(JSON.stringify(data), 'http://www.domain1.com');

        }
    })
</script>
六、cors 解决跨域问题
CORS是一个W3C标准,全称是"跨域资源共享"。它允许浏览器向跨源服务器,发出XMLHttpRequest请求,从而克服了AJAX只能同源使用的限制。CORS需要浏览器和服务器同时支持。目前,所有浏览器都支持该功能,IE浏览器不能低于IE10.整个CORS通信过程都是浏览器自动完成,不需要用户参与。对于开发者来说,CORS通信与同源的AJAX通信没有差别,代码完全一样。浏览器一旦发现AJAX请求跨源,就会自动添加一些附加的头部信息,有时还会多出一次附加的请求,但用户不会有感觉。因此,实现CORS通信的关键是服务器。只要服务器实现了CORS接口,就可以跨源通信
浏览器将CORS请求分成两类,简单请求和非简单请求.
只要同时满足以下两大条件,就属于简单请求。
(1)请求方法是一下三种方法之一:
    .HEAD
    .Get
    .post
 (2)HTTP的头信息不超出以下几种字段:
    .Accept
    .Accept-Language
    .Content-Language
    .Last-Event-Id
    .Content-Type:只限于三个值application/x-www-from-urlencodel、miltipart/form-data、text/plain
    凡是不同时满足上面两个条件,就属于非简单请求。
    浏览器对这两种请求的处理,是不一样的。
AJAX的GET和POST方法的解决方案,这属于简单请求
对于简单请求
服务端:设置以下参数
Access-Control-Allow-Origin:*
//*代表允许所有的域名访问,写www.abc.com的话就是只允许来自该域名的跨域请求
Access-Control-Allow-Credentials:true
//CORS请求默认不发送Cookie和HTTP认证信息。如果要把Cookie发到服务器,一方面要服务器同意,
指定Access-Control-Allow-Crendentuals字段。

客户端:
//需要在AJAX请求中打开withCredentails属性。
var xhr = new XMLHttpRequest();
xhr.withCredentials = true;

$.ajax({
    type:'POST',
    url:str,
    xhrFields:{withCredentails:true},
    contentType:'application/x-www-from-urlencoded',
    data:data,
    dataType:'json',
    success:function(result){},
    error:function(message){}
    
})
```检查发送出去的http请求,可以发现发出去的报文带上了Cookie和Origin的项,
服务器收到报文,检查Origin是否在它的Access-Control-Allow-Origin里面,是的话才提供服务。