跨源资源共享 (解决跨域问题)

目录

跨源资源共享 

        预检请求

        凭据请求

 

替代性跨源技术

        图片探测

        JSONP


跨源资源共享 

        通过XHR进行Ajax通信的一个主要限制是跨源安全策略。默认情况下,XHR只能访问与发起请求的页面在同一个域内的资源。这个安全限制可以防止某些恶意行为。不过,浏览器也需要支持合法跨源访问的能力。

        跨源资源共享(CORS,Cross-Origin Resource Sharing)定义了浏览器与服务器如何实现跨源通信。CORS背后的基本思路就是使用自定义的HTTP头部允许浏览器和服务器相互了解,以确实请求或响应应该成功还是失败。

        对于简单的请求,比如GET或POST请求,没有自定义头部,而且请求体是text/plain类型,这样的请求在发送时会有一个额外的头部叫Origin。Origin头部包含发送请求的页面的源(协议、域名和端口),以便服务器确定是否为其提供响应。

Origin头部的一个示例:

        Origin: http://www.nczonline.net

        如果服务器决定响应请求,那么应该发送Access-Control-Allow-Origin头部,包含相同的源;或者如果资源是公开的,那么就包含"*"。

例如:

        Access-Control-Allow-Origin: http://www.nczonline.net 

        如果没有这个头部,或者有但源不匹配,则表明不会响应浏览器请求。否则,服务器就会处理这个请求。注意,无论请求还是响应都不会包含cookie信息。

        现代浏览器通过XMLHttpRequest对象原生支持CORS。在尝试访问不同源的资源时,这个行为会被自动触发。要向不同域的源发送请求,可以使用标准XHR对象并给open()方法传入一个绝对URL 

let xhr = new XMLHttpRequest()

xhr.onreadystatechange = function() {
    if (xhr.readyState == 4) {
        if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304) {
            alert(xhr.responseText)
        } else {
            alert(" 请求失败,响应回来的状态码是: " + xhr.status)
        }
    }
}

xhr.open("get", "http://www.somewhere-else.com/page/", true)

xhr.send(null)

        跨域XHR对象允许访问status和statusText属性,也允许同步请求。出于安全考虑,跨域XHR对象也施加了一些额外限制。

  1. 不能使用setRequestHeader()设置自定义头部。
  2. 不能发送和接收cookie。
  3. getAllResponseHeaders()方法始终返回空字符串。

        因为无论同域还是跨域请求都使用同一个接口,所以最好在访问本地资源时使用相对URL,在访问远程资源时使用绝对URL。这样可以更明确地区分使用场景,同时避免出现访问本地资源时出现头部或cookie信息访问受限的问题。

        预检请求

                CORS通过一种叫预检请求(preflighted request)的服务器验证机制,允许使用自定义头部、除GET和POST之外的方法,以及不同请求体内容类型。在要发送涉及上述某种高级选项的请求时,会先向服务器发送一个“预检”请求。这个请求使用OPTIONS方法发送并包含以下头部。

Origin:与简单请求相同。

Access-Control-Request-Method:请求希望使用的方法。

Access-Control-Request-Headers:(可选)要使用的逗号分隔的自定义头部列表。

// 下面是一个假设的POST请求,包含自定义的NCZ头部:

Origin: http://www.nczonline.net

Access-Control-Request-Method: POST

Access-Control-Request-Headers: NCZ 

                在这个请求发送后,服务器可以确定是否允许这种类型的请求。服务器会通过在响应中发送如下头部与浏览器沟通这些信息。

Access-Control-Allow-Origin:与简单请求相同。
Access-Control-Allow-Methods:允许的方法(逗号分隔的列表)。
Access-Control-Allow-Headers:服务器允许的头部(逗号分隔的列表)。
Access-Control-Max-Age:缓存预检请求的秒数。

// 例如

Access-Control-Allow-Origin: http://www.nczonline.net

Access-Control-Allow-Methods: POST, GET

Access-Control-Allow-Headers: NCZ

Access-Control-Max-Age: 1728000 

                 预检请求返回后,结果会按响应指定的时间缓存一段时间。换句话说,只有第一次发送这种类型的请求时才会多发送一次额外的HTTP请求。

        凭据请求

                默认情况下,跨源请求不提供凭据(cookie、HTTP认证和客户端SSL证书)。可以通过将withCredentials属性设置为true来表明请求会发送凭据。如果服务器允许带凭据的请求,那么可以在响应中包含如下HTTP头部:

Access-Control-Allow-Credentials: true

                如果发送了凭据请求而服务器返回的响应中没有这个头部,则浏览器不会把响应交给JavaScript(responseText是空字符串,status是0,onerror()被调用)。注意,服务器也可以在预检请求的响应中发送这个HTTP头部,以表明这个源允许发送凭据请求。

 

替代性跨源技术

        CORS出现之前,实现跨源Ajax通信是有点麻烦的。开发者需要依赖能够执行跨源请求的DOM特性,在不使用XHR对象情况下发送某种类型的请求。虽然CORS目前已经得到广泛支持,但这些技术仍然没有过时,因为它们不需要修改服务器。

        图片探测

                图片探测是利用<img>标签实现跨域通信的最早的一种技术。任何页面都可以跨域加载图片而不必担心限制,因此这也是在线广告跟踪的主要方式。可以动态创建图片,然后通过它们的onload和onerror事件处理程序得知何时收到响应。

                这种动态创建图片的技术经常用于图片探测(image pings)。图片探测是与服务器之间简单、跨域、单向的通信。数据通过查询字符串发送,响应可以随意设置,不过一般是位图图片或值为204的状态码。浏览器通过图片探测拿不到任何数据,但可以通过监听onload和onerror事件知道什么时候能接收到响应。下面看一个例子:

let img = new Image()

img.onload = img.onerror = function() { alert("Done!") }

img.src = "http://www.example.com/test?name=Nicholas"

                这个例子创建了一个新的Image实例,然后为它的onload和onerror事件处理程序添加了同一个函数。这样可以确保请求完成时无论什么响应都会收到通知。设置完src属性之后请求就开始了,这个例子向服务器发送了一个name值。

                图片探测频繁用于跟踪用户在页面上的点击操作或动态显示广告。当然,图片探测的缺点是只能发送GET请求和无法获取服务器响应的内容。这也是只能利用图片探测实现浏览器与服务器单向通信的原因。

        JSONP

                JSONP是“JSON with padding”的简写,是在Web服务上流行的一种JSON变体。JSONP看起来跟JSON一样,只是会被包在一个函数调用里,比如:

callback({ "name":"Nicholas" });

                JSONP格式包含两个部分:回调和数据。回调是在页面接收到响应之后应该调用的函数,通常回调函数的名称是通过请求来动态指定的。而数据就是作为参数传给回调函数的JSON数据。下面是一个典型的JSONP请求: 

http://freegeoip.net/json/?callback=handleRespons

                这个JSONP请求的URL是一个地理位置服务。JSONP服务通常支持以查询字符串形式指定回调函数的名称。比如这个例子就把回调函数的名字指定为handleResponse()。

                JSONP调用是通过动态创建<script>元素并为src属性指定跨域URL实现的。此时的<script>与<img>元素类似,能够不受限制地从其他域加载资源。因为JSONP是有效的JavaScript,所以JSONP响应在被加载完成之后会立即执行。比如下面这个例子:

function handleResponse(response) {
    console.log(`You're at IP address ${response.ip}, which is in ${response.city},${response.region_name}`)
    }
let script = document.createElement("script")

script.src = "http://freegeoip.net/json/?callback=handleResponse"

document.body.insertBefore(script, document.body.firstChild)

                这个例子会显示从地理位置服务获取的IP地址及位置信息。

                JSONP由于其简单易用,在开发者中非常流行。相比于图片探测,使用JSONP可以直接访问响应,实现浏览器与服务器的双向通信。不过JSONP也有一些缺点。

                首先,JSONP是从不同的域拉取可执行代码。如果这个域并不可信,则可能在响应中加入恶意内容。此时除了完全删除JSONP没有其他办法。在使用不受控的Web服务时,一定要保证是可以信任的。

                第二个缺点是不好确定JSONP请求是否失败。虽然HTML5规定了<script>元素的onerror事件处理程序,但还没有被任何浏览器实现。为此,开发者经常使用计时器来决定是否放弃等待响应。这种方式并不准确,毕竟不同用户的网络连接速度和带宽是不一样的。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值