详说跨域和ajax

             我之前一直搞不懂跨域,ajax都理解错了,这让我很受伤,所以现在让我理一理,如果说错了欢迎补充!

1.首先我们说一下ajax是什么?

1) AJAX 是一种在无需重新加载整个网页的情况下,能够更新部分网页的技术。(注意!ajax不是跨域方式!它是一种技术)

ajax= Asynchronous JavaScript and XML(异步的 JavaScript 和 XML)

我理解的ajax就是能请求接口并且部分更新数据。浏览器使用“同源策略”限制了AJAX技术获取数据的范围和能力,不同域之间无法通过AJAX技术获取资源。这是需要跨域获取资源的主要情景。

2)XMLHttpequest 是 AJAX 的基础

XMLHttpRequest 用于在后台与服务器交换数据。这意味着可以在不重新加载整个网页的情况下,对网页的某部分进行更新。

3)ajax实现方式

a.创建XMLHttpRequest 对象

b.向服务器发送请求

如需将请求发送到服务器,我们使用 XMLHttpRequest 对象的 open() 和 send() 方法:

c.服务器响应

获得来自服务器的响应,可以使用 XMLHttpRequest 对象的 responseText 或 responseXML 属性。

d.onreadystatechange 事件,使用 Callback 函数

当请求被发送到服务器时,我们需要执行一些基于响应的任务。

每当 readyState 改变时,就会触发 onreadystatechange 事件。

onreadystatechange 事件被触发 5 次(0 - 4),对应着 readyState 的每个变化。

    2.同源是什么?

1) 整个互联网世界的数据要么存储在 服务端 (即服务器,如数据库,硬盘等)中,要么存储在 客户端 (即浏览器,如cookie,LocalStorage,sessionStorage)中。互联网数据的传输实际上就是客户端与服务端之间的交互。

2)浏览器所遵守的“同源策略”是指:限制不同源之间执行特定操作。

这涉及到两个问题:什么是“源”?,以及“特定操作”是指什么?

一个源由协议,域名和端口三部分组成,这三者任一一个不同都会被浏览器识别为不同的源; 特定操作是指:

       a.读取 Cookie,LocalStorage 和 IndexDB;

       b.获取 DOM 元素和js对象;

       c .发送 AJAX 请求;(浏览器的“同源策略” 只是阻止了通过AJAX技术跨域获取资源,而并没有禁止跨域获取资源这件事本身)

3) 所以如果没有同源策略会怎么样

       a.没有同源策略限制的接口请求

有一个小小的东西叫cookie大家应该知道,一般用来处理登录等场景,目的是让服务端知道谁发出的这次请求。如果你请求了接口进行登录,服务端验证通过后会在响应头加入Set-Cookie字段,然后下次再发请求的时候,浏览器会自动将cookie附加在HTTP请求的头字段Cookie中,服务端就能知道这个用户已经登录过了。知道这个之后,我们来看场景:
       比如你在看微信时打开了另外一个不法网站,由于没有同源策略的限制,他向微信发起了请求,就像上面说的服务端验证通过后会在响应头加入Set-Cookie字段,然后下次再发请求的时候,浏览器会自动将cookie附加在HTTP请求的头字段Cookie中,这样一来,这个不法网站就相当于登录了你的账号,这就是传说中的CSRF攻击。由于cookie是明文的,像XSS攻击就可以去获取到cookie,这样还是很不安全,但由于cookie、Cookie/Session的机制与安全,在服务端可以设置httpOnly,使得前端无法操作cookie;设置secure,则保证在https的加密通信中传输以防截获。


          b.没有同源策略限制的Dom查询

            有人给你发了一个和一摸一样界面的伪银行网站www.yinghang.com,告诉你账号有风险,你登录进去,我们看看这时候这个网站做了什么?实际网站为ww.yinhang.com
// HTML

<iframe name="yinhang" src="www.yinhang.com"></iframe>

// JS

// 由于没有同源策略的限制,钓鱼网站可以直接拿到别的网站的Dom

const iframe = window.frames['yinhang']

const node = iframe.document.getElementById('你输入账号密码的Input')

console.log(`拿到了这个${node},我还拿不到你刚刚输入的账号密码吗`)

由此我们知道,同源策略确实能规避一些危险,不是说有了同源策略就安全,只是说同源策略是一种浏览器最基本的安全机制,毕竟能提高一点攻击的成本。

3.最最重要的来了:     跨域方式!

1)JSONP

<script>和<img>是本身就不受跨域限制的两个标签

通过script标签引入一个js文件,这个js文件载入成功后会执行我们在url参数中指定的函数,并且会把我们需要的json数据作为参数传入。所以jsonp是需要服务器端的页面进行相应的配合的。

实现方式有原生js,jquery ajax,vuejs(promise执行回调函数)

1.)原生实现:

 <script>
    var script = document.createElement('script');
    script.type = 'text/javascript';

    // 传参并指定回调执行函数为onBack
    script.src = 'http://www.domain2.com:8080/login?user=admin&callback=onBack';
    document.head.appendChild(script);

    // 回调执行函数
    function onBack(res) {
        alert(JSON.stringify(res));
    }
 </script>

服务端返回如下(返回时即执行全局函数):

onBack({"status": true, "user": "admin"})

2.)jquery ajax:

$.ajax({
    url: 'http://www.domain2.com:8080/login',
    type: 'get',
    dataType: 'jsonp',  // 请求方式为jsonp
    jsonpCallback: "onBack",    // 自定义回调函数名
    data: {}
});

3.)vue.js:

this.$http.jsonp('http://www.domain2.com:8080/login', {
    params: {},
    jsonp: 'onBack'
}).then((res) => {
    console.log(res); 
})

后端node.js代码示例:

var querystring = require('querystring');
var http = require('http');
var server = http.createServer();

server.on('request', function(req, res) {
    var params = qs.parse(req.url.split('?')[1]);
    var fn = params.callback;

    // jsonp返回设置
    res.writeHead(200, { 'Content-Type': 'text/javascript' });
    res.write(fn + '(' + JSON.stringify(params) + ')');

    res.end();
});

server.listen('8080');
console.log('Server is running at port 8080...');

缺点:这种方式无法发送post请求(这里

JSONP技术与AJAX技术无关:虽然同样牵扯到跨域获取资源这个主题,但我们应该已经清楚的看到,JSONP的本质是绕过AJAX获取资源的机制,使用原始的src属性获取异域资源

那如何结合jsonp和ajax跨域

2)跨域资源共享(CORS)

http://www.ruanyifeng.com/blog/2016/04/cors.html

它允许浏览器向跨源服务器,发出XMLHttpRequest请求,从而克服了AJAX只能同源使用的限制。只要服务器实现了CORS接口,就可以跨源通信。对于简单请求,浏览器直接发出CORS请求。具体来说,就是在头信息之中,增加一个Origin字段。

Origin字段用来说明,本次请求来自哪个源(协议 + 域名 + 端口)。服务器根据这个值,决定是否同意这次请求

普通跨域请求:只服务端设置Access-Control-Allow-Origin即可,前端无须设置,若要带cookie请求:前后端都需要设置。

Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Headers: X-PINGOTHER, Content-Type
Access-Control-Max-Age: 86400

跨域请求默认不会携带Cookie信息,如果需要携带,请配置下述参数:

"Access-Control-Allow-Credentials": true
// Ajax设置
"withCredentials": true

3) Nodejs中间件代理跨域

node中间件实现跨域代理,原理大致与nginx相同,都是通过启一个代理服务器,实现数据的转发,也可以通过设置cookieDomainRewrite参数修改响应头中cookie中域名,实现当前域的cookie写入,方便接口登录认证。

4)通过修改document.domain来跨子域,只适用于不同子域的框架间的交互。

如果你想通过ajax的方法去与不同子域的页面交互,除了使用jsonp的方法外,还可以用一个隐藏的iframe来做一个代理。原理就是让这个iframe载入一个与你想要通过ajax获取数据的目标页面处在相同的域的页面,所以这个iframe中的页面是可以正常使用ajax去获取你要的数据的,然后就是通过我们刚刚讲得修改document.domain的方法,让我们能通过js完全控制这个iframe,这样我们就可以让iframe去发送ajax请求,然后收到的数据我们也可以获得了。

前提条件:这两个域名必须属于同一个基础域名!而且所用的协议,端口都要一致,否则无法利用document.domain进行跨域,所以只能跨子域

​ 在根域范围内,允许把domain属性的值设置为它的上一级域。例如,在”aaa.xxx.com”域内,可以把domain设置为 “xxx.com” 但不能设置为 “xxx.org” 或者”com”。

​ 现在存在两个域名aaa.xxx.com和bbb.xxx.com。在aaa下嵌入bbb的页面,由于其document.name不一致,无法在aaa下操作bbb的js。可以在aaa和bbb下通过js将document.name = 'xxx.com';设置一致,来达到互相访问的作用。

5)使用window.name来进行跨域

window对象有个name属性,该属性有个特征:即在一个窗口(window)的生命周期内,窗口载入的所有的页面都是共享一个window.name的,每个页面对window.name都有读写的权限,window.name是持久存在一个窗口载入过的所有页面中的,并不会因新页面的载入而进行重置。

使用一个隐藏的iframe来充当一个中间人角色,由iframe去获取data.html的数据,然后a.html再去得到iframe获取到的数据。

var proxy = function(url, callback) {
    var state = 0;
    var iframe = document.createElement('iframe');

    // 加载跨域页面
    iframe.src = url;

    // onload事件会触发2次,第1次加载跨域页,并留存数据于window.name
    iframe.onload = function() {
        if (state === 1) {
            // 第2次onload(同域proxy页)成功后,读取同域window.name中数据
            callback(iframe.contentWindow.name);
            destoryFrame();

        } else if (state === 0) {
            // 第1次onload(跨域页)成功后,切换到同域代理页面
            iframe.contentWindow.location = 'http://www.domain1.com/proxy.html';
            state = 1;
        }
    };

    document.body.appendChild(iframe);

    // 获取数据以后销毁这个iframe,释放内存;这也保证了安全(不被其他域frame js访问)
    function destoryFrame() {
        iframe.contentWindow.document.write('');
        iframe.contentWindow.close();
        document.body.removeChild(iframe);
    }
};

// 请求跨域b页面数据
proxy('http://www.domain2.com/b.html', function(data){
    alert(data);
});

6)使用HTML5中新引进的window.postMessage方法来跨域传送数据适用于两个异域客户端

可以使用它来向其它的window对象发送消息,无论这个window对象是属于同源或不同源

调用postMessage方法的window对象是指要接收消息的那一个window对象,该方法的第一个参数message为要发送的消息,类型只能为字符串;第二个参数targetOrigin用来限定接收消息的那个window对象所在的域,如果不想限定域,可以使用通配符 * 。

需要接收消息的window对象,可是通过监听自身的message事件来获取传过来的消息,消息内容储存在该事件对象的data属性中。

让我们用具体的业务场景与代码进一步说明,假如我们的页面现在有两个窗口,窗口1命名为“window_1”, 窗口2命名为“window_2”,当然,窗口1与窗口2的“域”是不同的,我们的需求是由窗口1向窗口2发送数据,而当窗口2接收到数据时,将数据再返回给窗口1。先让我们看看窗口1script标签内的代码:

// window_1 域名为 http://winodow1.com:8080
window.postMessage("Hi, How are you!", "http://window2.com:8080")

可以看到,postMessage函数接收两个参数,第一个为要发送的信息(可以是任何JavaScript类型数据,但部分浏览器只支持字符串格式),第二个为信息发送的目标地址。让我们再看看窗口2script标签内的代码:

// window_2 域名为 http://window2.com:8080
window.addEventListener("message", receiveMessage, false)

function receiveMessage(event) {
    // 对于Chorme,origin属性为originalEvent.origin属性
    var origin = event.origin || event.originalEvent.origin
    if (origin !== "http://window1.com:8080") {
        return 
    }
    window.postMessage("I\'m ok", "http://window1.com:8080")
}

看到了吗,我们在window上绑定了一个事件监听函数,监听message事件。一旦我们接收到其他域通过postMessage发送的信息,就会触发我们的receiveMessage回调函数。该函数会首先检查发送信息的域是否是我们想要的(之后我们会对此详细说明),如果验证成功则会像窗口1发送一条消息。

7) WebSocket

是一种 在单个TCP连接上进行全双工通讯的协议 。 为了弥补在 某些情景 下使用HTTP协议通信的一些不足。 某些情景 具体是指“ 服务端与客户端的双向通信 ”。三次握手后 服务器向客户端派发信使与浏览器互发信息,转发资源。 当客户端与服务端创建WebSocket连接后,本身就可以天然的实现跨域资源共享 ,WebSocket协议本身就不受浏览器“同源策略”的限制

让我们也同样构建一个基于WebSocket协议的心智模型,在这个心智模型中,服务端扮演的角色发生了一些改变,服务端不再只是一个“守门人”,同时它也运营着一个和客户端一样的“邮局”,也就是说,他也拥有了可以向客户端发送数据的能力。至此一个完整的基于WebSocket协议的通信流程为:

客户端派发一个信使向服务器送信,服务器扮演的“守门人”检查信件,发现信件中写到“让我们用更加潮流的WebSocket方式交流吧”,服务器在在信件末尾添加上一句“没问题,浏览器伙计”,让信使原路返回告知浏览器。当浏览器再次向服务器告知收到消息时(第三次握手),服务器就开始运转“邮局”,向客户端派发信使与浏览器互发信息,转发资源。

下面是客户端告知服务端要升级为WebSocket协议的报头:

GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13
Origin: http://example.com

下面是服务端向客户端返回的响应报头:

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk=
Sec-WebSocket-Protocol: chat

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值