什么是跨域?为什么要禁止跨域?怎样跨域?

 

什么是跨域

现代浏览器出于安全考虑,都会去遵守一个叫做“同源策略”的约定,同源的意思是两个地址的协议、域名、端口号都相同的情况下,才叫同源。这个时候两个地址才可以相互访问 cookie、localStorage、sessionStorage、发送 ajax 请求,如果三者有一个不同,就是不同源,这时再去访问这些资源就叫做跨域

为什么禁止跨域

跨域访问会造成很多安全问题,下面我们来看一下常见的两种跨域请求:

  • ajax 请求:A.com 中 请求 B.com 的接口,这个时候请求会带上 B.com 的 cookie,通常会有登录信息之类的,我们称之为 CSRF攻击
  • dom 操作:A.com 中使用 iframe 嵌套了 B.com 的广告,这时候如果 B.com 对 A.com 中的 dom 任意操作,那么主网站 A.com 就崩了
  • 字体文件:字体文件也有跨域限制,只是我还不明白为什么

那么哪些请求时不会造成跨域的呢

  • js、css、image 等静态文件
  • form 表单提交

因为这些请求不会携带 cookie,也就没有跨域限制

怎么跨域

浏览器对某些请求限制了跨域访问,但是实际开发中,我们又确实需要去跨域访问,怎么办呢?

jsonp

由于访问 js 文件是没有跨域限制的,我们可以利用这一点来做跨域。具体过程如下:

客户端预先定义好一个 function 用来接收数据,然后动态的创建一个 script 插入到页面中去后端请求数据

function run (data) {
    console.log(data.name)
}

var script = document.createElement('script')
script.src = 'http://xxx.js?callback=run'
document.body.appendChild(script)

服务端通过 callback 指定的函数名,将数据传入其中,这样 js 到达客户端就能直接执行函数,把数据传入预先定义好的函数中

run({ name: '张三' })

优点:

  • 无跨域限制
  • 兼容性好,古老的浏览器也可以使用,无需XMLHttpRequest或ActiveX支持

缺点:

  • 只能发起 get,不能发起 post
  • 只支持 http 请求,不能解决两个不同源页面之间的通信问题
  • 不像 ajax 可以返回详细的状态码,用于错误处理
  • 页面上会频繁的插入 script 元素,如不及时清除,页面将变得卡顿

window.name

该属性有以下特征

  • 每个窗口都会有自己独立的 window 及 window.name
  • 在一个窗口的生命周期中(被关闭前),窗口中载入的所有页面都共享一个 window.name,且每个页面对 window.name 都有读写的权限
  • window.name 一直存在当前窗口,即使有新的页面进来也不会改变它的值
  • window.name 可以存储不超过 2M 的数据,数据格式可以自定义

document.domain + iframe 跨子域

默认情况下 document.domain 存放的是载入文档的主机名,例如 a.main.com。可以手动修改这个值,但是有限制:

  • 只能改成当前域名 a.main.com 或上级域名 main.com。
  • 必须包含一个 ".",也就是说不能设置成顶级域名 com

利用这一点,对于主域名相同,而子域名不同的两个页面,例如 a.main.com 使用 iframe 嵌入了 b.main.com,可以使用 document.domain 来跨域。

当在 a 页面直接通过 iframe.contentWindow 想去操作 b 页面,或者 b 页面通过 window.top、window.parent 想去操作 a 页面时,就是出现跨域限制。

这个时候我们可以在 a、b 两个页面同时设置 document.domain = 'main.com',然后再去相互访问就没有问题了

location.hash + iframe

我们都知道,页面地址中 hash 值的改变是不会引起页面刷新的,所以我们可以利用 hash 值来在不同域中传递数据。例如

a.com 使用 iframe 嵌入了 b.com。a 页面要向 b 页面传递数据就可以通过改变 b 页面的 hash 值,具体过程如下:

a 页面:iframe.src = b.com#age=18

b 页面:window.onhashchange = function () {console.log(localtion.hash)}

缺点:

  • 只能从父窗口向子窗口单项传递数据
  • 由于数据是放在url上的,所以有长度限制

postMessage

postMessage 是 HTML5 XMLHttpRequest Level 2 中的 API,它可以解决以下几个需求场景:

  • 多窗口之间的消息传递
  • 主页面与嵌入在 iframe 内的页面之间的消息传递

举例说明一下用法,比如 a 页面内通过 iframe 嵌入了 b 页面,现在要互相传递数据

a -> b

  • a 页面:iframe.contentWindow.postMessage('xxxx', 'http://b.com')
  • b 页面:window.addEventListener('message', function(e) { console.log(e.data) })

b -> a

  • a 页面:window.addEventListener('message', function(e) { console.log(e.data) })
  • b 页面:window.top.postMessage('xxxxx', 'http://a.com')

这里需要注意几点

  • postMessage 的第一个参数是消息体,格式无限制,可以是 String、Array、Object
  • 第二个参数是目标窗口的地址,遵守同源策略,如果不同源,则目标窗口接收不到消息。如果传入的时 “*”,则任意窗口都可以获取到

服务器代理

浏览器有跨域限制,但是服务器不存在跨域问题。所以可以浏览器可以把需要跨域的请求先发给服务器,由服务器去请求资源,然后再把结果返回给浏览器。

典型的应用场景:前后端分离

前后端分别有一个域名,前端访问服务端接口就出现了跨域限制,这个时候前端就可以在所有的服务端接口前加上一个标志如 /api,然后在前端服务器和后端服务器前在加一层分发,带 /api接口的都访问后端服务器,并去掉 /api,其他的都访问前端服务器。

WebSocket协议跨域

WebSocket  protocol 是 HTML5 中的一种新协议,它实现了浏览器与服务端全双工通信,同时支持跨域。拿比较知名的websocket 库 Socket.io 来举例说明它的用法:

浏览器

var socket = io('http://localhost:8080')
socket.on('connect', function () {
    socket.on('message', function(msg) {
        console.log('收到消息:' + msg)
    })
    socket.on('disconnect', function() {
        console.log('关闭连接')
    })
})

服务器

socket.listen(server).on('connection', function(client) {
    // 接收信息
    client.on('message', function(msg) {
        client.send('hello:' + msg);
        console.log('data from client: ---> ' + msg);
    });

    // 断开处理
    client.on('disconnect', function() {
        console.log('Client socket has closed.'); 
    });
});

跨域资源访问(CORS)

CORS 时 W3C 的标准,它允许浏览器向跨源服务器发起 ajax 请求,从而达到跨域访问的目的。这是当前比较流行的跨域方式,知识点也比较多,下面我们一一展开。

浏览器将 CORS 请求分为两类:简单请求(simple request)和非简单请求(not-so-simple request)

简单请求需要同时满足以下两个条件:

1、请求方法是一下三种之一

  • HEAD
  • GET
  • POST

2、请求头不能超出一下几个字段

  • Accept
  • Accept-Language
  • Content-Language
  • Last-Event-ID
  • Context-Type 只限于三个值:application/x-www-form-urlencoded、multipart-part/form-data、text/plain

除此以外的就是非简单请求

简单请求

当浏览器判断此次是一个跨域的 ajax 简单请求时,会自动在 request header 中加入 Origin 字段,用来标识本次请求来自哪个源(协议、域名、端口号),服务器根据这个值决定是否允许本次请求。

如果服务器同意本次请求,会在 response header 中加入以下几个字段

  • Allow-Control-Allow-Origin:它的值为请求时的 Origin 字段或者 *,表示允许跨域的来源,* 表示允许所有的源来跨域访问
  • Allow-Control-Allow-Credentials:布尔值,表示服务器是否接受来自浏览器跨域请求时所携带的 cookie
  • Allow-Control-Expose-Headers:浏览器通过 XMLHttpRequest 对象的 getResponseHeader 方法只能拿到 6 个基本字段:Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Prama。如果浏览器想拿到其他字段,就需要服务器在改字段中指定

默认情况下,CORS 请求是不携带 cookie 的。如果需要在 request header 中放入 cookie,除了上面说的服务器需要在 response header 中设置 Allow-Control-Allow-Credentials: true,表示愿意接受跨域 cookie 之外,浏览器也需要设置一个参数表示允许发送 cookie

xhr.withCredentials = true;

非简单请求

我们先来看一个 http 请求

var xhr = new XMLHttpRequest()
xhr.open('DELETE', 'http://xxx/deluser', true)
xhr.setRequestHeader('token', '888')
xhr.setRequestHeader('Content-Type', 'application/json;charset=UTF-8')
xhr.send()

预检请求(prelight)

根据上面我们对简单请求的约定,发现这条请求的方法、头字段、Content-Type 的值都不符合简单请求的约定。所以浏览器会判断出此次的 http 请求是一次非简单请求。

浏览器会先发出一条 http 请求询问服务器,当前源是否在服务器的允许名单之内,以及可以使用哪些头信息。我们称这次询问为“预检”(prelight),预检的请求方法是 options,request header 中会有这么几个重要的字段:

  • Origin:表示请求来自哪个源
  • Access-Control-Request-Method:表示浏览器的 CORS 请求将会用到哪些 http 方法
  • Access-Control-Request-Headers:该字段是以都好分割的字符串,表示 CORS 请求会额外携带的头字段

服务器接收到“预检”请求后,会检查上述三个字段,如果允许跨域就做出相应头信息

  • Access-Control-Allow-Origin: http://api.bob.com
  • Access-Control-Allow-Methods: GET, POST, DELETE
  • Access-Control-Allow-Headers: token
  • Access-Control-Allow-Credentials: true
  • Content-Type: application/json; charset=utf-8

正常请求

浏览器在接收到服务器的“预检”相应后,判断出服务器允许跨域。这时便会发送正常的请求,只是在每次请求中,都会往 request header 中加入 Origin 字段,相应的服务器也会在每次相应中,在 response header 加入 Access-Control-Allow-Origin 字段

  • 7
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值