跨域, 必知必解

308 阅读6分钟

关于跨域,之所以会出现是因为"同源策略"

那什么是同源策略?我们说当两个地址如果在协议、域名、端口号其中之一不一样,我们称为跨域。

为什么不支持跨域?

cookie,localStorage 不支持跨域

当用户登录银行网站,而http是没有状态的,不知道是谁登陆,网站会发给客户个cookie,给你发个sessionID标识是当前客户,假设当前没有同源问题,然后用户访问了一个恶意网站,然后给恶意网站发送ajax请求,请求的时候,将浏览器给客户的id给了恶意网站,然后后恶意网站可以拿着客户给的id在去访问银行,相当于伪造用户,不安全

DOM元素 iframe

在我们的页面嵌入别人的页面,我们在此页面操作别的页面,前提是必须是同域,假设用户输入账号密码登录,我的网站可以操作里面的页面,那么用户的账号密码就能被看到,着也不安全

ajax不支持跨域 接口只要知道链接,都可以访问

这么多问题,我们为什么还要跨域,常见比如前后端分离?

前后端不在同一个服务器地址上,这两者想实现通信,,怎么通信?需要一些手段,,有一些是协商好的,或者聪明点的办法

  1. jsop 缺点 xxs攻击
  2. cors
  3. postMessage 两个页面之间进行通信
  4. window.name 到底name能干嘛
  5. location.hash sta框架比较多
  6. http-propxy webpack 有个代理 有个中间件
  7. nginx 配
  8. websocket 页面之间的通信,但是没有跨域的问题
  9. 图像ping 和Comet
  10. document.domain 二级域名和一级域名,同一个域下的,子域和父域的
  • jsop 缺点

最典型的是百度搜索框

这是一个标准的jsonp接口,它原理呢,就是利用我们<link>,script,img,等引入方式是不受通源策略的,打开地址https://sp0.baidu.com/5a1Fazu8AA54nxGko9WTAnF6hhy/su?wd=a&cb=show出现show({q:"b",p:false,s:["baidu","bt","btchina","beyond","bbs","bbc","blog","bobo组合","bb霜"]});

function show(data){
    console.log(data)
}
show({q:"b",p:false,s:["baidu","bt","btchina","beyond","bbs","bbc","blog","bobo组合","bb霜"]}); 

等同于

function show(data){
    console.log(data)
}
//    show({q:"b",p:false,s:["baidu","bt","btchina","beyond","bbs","bbc","blog","bobo组合","bb霜"]}); 

<script src="https://sp0.baidu.com/5a1Fazu8AA54nxGko9WTAnF6hhy/su?wd=a&cb=show"></script>//这个文件域实现跨域

我们可能从来没写过什么全局函数,也没说是怎么声明的,我们怎么用呢?一般我们会用第三方的包,也可以自己写

//<!--<script>-->
        function jsonp({url,params,cb}){ //params:{wd:'a'} cb:'show'
            return new Promise((resolve,reject) => {
                let script = document.createElement('script');
                window[cb] = function(data){
                    resolve(data);
                    document.body.removeChild(script)
                }
                params = {...params,cb} // params = {wd:'a',cb:'show'}
                let arr = [];
                for(let key in params){
                    arr.push(`${key}=${params[key]}`);
                } // arr[wd='a','cb='show']
                script.src = `${url}?${arr.join('&')}`;// arr.join('&') = wd='a'&'cb='show'
                document.body.appendChild(script);
            })

        }
        //jsonp会帮我们创建回调,创建script标签
        jsonp({
            url:'https://sp0.baidu.com/5a1Fazu8AA54nxGko9WTAnF6hhy/su',
            params:{wd:'a'},
            cb:'show'
        }).then(data => {
            console.log(data)
        })
    //    show({q:"b",p:false,s:["baidu","bt","btchina","beyond","bbs","bbc","blog","bobo组合","bb霜"]}); 
//    </script>
  //  <!-- <script src="https://sp0.baidu.com/5a1Fazu8AA54nxGko9WTAnF6hhy/su?wd=a&cb=show"></script> -->

由于我们是使用引入script标签的方法,因此我们只能请求get方法,而且引入进来的script脚本可能会受到恶意攻击(xss),虽然很常用,但是不建议使用。 h5请参考www.runoob.com/json/json-j…

  • cors      

这个跟前端几乎没有 关系,一般类似于后台的白名单,而且后台仅仅需要加几行代码

//res
let xhr = new XMLHttpRequest;
document.cookie = 'name=哇美女';
xhr.withCredentials = true; //cookie跨域需要上传凭证
xhr.open('PUT','http://localhost:4000/getData',true);
xhr.setRequestHeader('name','zdl');
xhr.onreadystatechange = function(){
    if(xhr.readyState === 4){
        if(xhr.status >= 200 && xhr.status < 300 || xhr.status === 304){
            console.log(xhr.response)
        }
    }
}
//res
let whitlist = ["http://localhost:3000"];
app.use(function(req,res,next){
    let origin = req.hearders.origin;
    if(whitlist.includs(origin)){
        res.setHeader('Access-control-origin',origin);//源
        res.setHeader('Access-control-Headers',name);//携带头
        res.setHeader('Access-control-Method',put);//方式
        res.setHeader('Access-control-Credentials',true);//cookie可以跨域
        res.setHeader('Access-control-Expose-Headers','name');//允许返回头
    }
})

  • postMessage 两个页面之间进行通信

简单来说就是iframe通信

//目前是3000 端口a页面   http://localhost:3000/a.html
//<iframe src="http://localhost:4000/b.html" id = "frame" load()></iframe>
function load(){
    let frame = document.getElementById('frame');
    frame.contentWindow.postMessage('我美吗''http://localhost:4000/b.htm');
    window.onmessage = function(e){
        console.log(e.data)
    }
}

//目前是4000 端口b页面   http://localhost:4000/b.html
//<iframe src="http://localhost:4000/b.html" id = "frame" load()></iframe>
window.onmessage= function(e){
    console.log(e.data);
    e.source.postMessage('你很美',e.origin)
}
  • window.name 到底name能干嘛
  1. a和b是同域的 http://localhost:3000
  2. c是独立的 http://localhost:4000
  3. a获取c的数据
  4. a先引用c, c把值放到window.name,把a的引用地址改到b
//目前是3000 端口a页面   http://localhost:3000/a.html
//<iframe src="http://localhost:4000/c.html" id = "frame" onload = "load()"></iframe>
function load(){
    let first = true;
    if(first){//第一次加载,c将name放进去了
        let iframe = document.getElementById('frame');
        iframe.src = 'http://localhost:3000/b.html';//改变引用地址,出发load事件
        first = false;
        frame.contentWindow.postMessage('我美吗''http://localhost:3000/b.htm');
        window.onmessage = function(e){
            console.log(e.data)
        }
    }else{//a和b是同域
        iframe.contentWindow.name //此时a和b是同域,可以取值
    }
    
}
//c页面
window.name = 'fsfa'
  • location.hash sta框架比较多

路径后面的hash也可以通信

目的: a访问c

a将hash值传递给c,c可以收到hash值,但是a不能拿c的结果,c以同样的方式传递给b ,然后b将结果放到hash中

//a页面,这样c可以取到hash值
//<iframe src="http://localhost:4000/c.html#fdhd" id = "frame" </iframe>
//c
console.log(location.hash);
let iframe = document.creatElement('iframe');
iframe.src = "http://localhost:3000/b.html#dsafaf";
document.body.apendChild(iframe);//将hach传递给b
//b b和a同域,可以传递
window.parent.parent.location.hash = location.hash

//a
window.onhashchange = function(){
    console.log(location.hash)
}

比较少用,同样是通过iframe的src传递 src = http://localhost:4000/c.html#dsfdf 只不过是通过hash传递

  • http-propxy webpack 有个代理 有个中间件

  • nginx 配或者命令

  • websocket 页面之间的通信,但是没有跨域的问题,同样能实现数据之间的传递

优点就是传播速度快,传播平等,与ajax不同的是服务器能主动和websocket通信,双工,ajax是单双工 请参考阮一峰websoke介绍,很详细

//高级api不兼容,,有个库 socket.io兼容
let socket = new WebSocket('ws://localhost:3000');
//链接成功会触发onopen 事件
ws.onopen = function(evt) { 
  console.log("Connection open ..."); 
  ws.send("Hello WebSockets!");
};
//打开
//接收
ws.onmessage = function(evt) {
  console.log( "Received Message: " + evt.data);
  ws.close();
};
//关闭
ws.onclose = function(evt) {
  console.log("Connection closed.");
};      
  • document.domain

它解决了某些问题,但不是所有的,

域名关系:二级域名和一级域名

找到hosts配置文件配置域名:

127.0.0.1.a.ah.cn

127.0.0.1.b.ah.cn

同一个域下的,子域和父域的 http://localhost:3000/a.htmlhttp://localhost/two:3000/b.html

//a是http://a.ah.cn:3000/a.html
//<iframe src="http://b.ah.cn:3000/b.html" id = "frame" onload="load"></iframe>
document.domain = 'ah.cn';//解决方法各自加domain
function load(){
    console.log(frame.contentWindow.a)//一般会跨域
}

//b
document.domain = 'ah.cn';
var a = 100