跨域资源共享的使用

前言

页面中常常会有需要跨域通信的需求实现,我们知道浏览器的同源策略是不允许不同域之间的相互通信的(这里不深究域的定义及如何才算跨域),比如a.com有b.com想要的数据,那么在b.com页面中发送ajax请求到a.com是不允许的,相信大家都知道一些跨域通信的实现方法:

  • JSON-P(安全性不好)
  • window.name + iframe(实现的方式恶心)
  • window.postMessage(HTML5)
  • proxy(麻烦的部署及维护)
  • ...

跨域资源共享(Cross-Origin Resource Sharing)是W3C的一项规定,它规定了在浏览器中,基于XMLHttpRequest对象的跨域请求通信的原理,基本上保持了原有对象的用法。

CORS需要服务器端及客户端双方面的更改支持。本文主要介绍如何发起一个跨域请求和如何在服务器端支持CORS。

兼容性

  • Chrome 3+
  • Firefox 3.5+
  • Opera 12+
  • Safari 4+
  • Internet Explorer 8+

发起一个跨域请求

  1. 第一步新建XMLHttpRequest对象
function get_CORS_XHR(method, url) {
    var xhr = new XMLHttpRequest();
    if ("withCredentials" in xhr) {
        // "withCredentials"属性只存在于XMLHttpRequest2对象中
        // Chrome, Firefox, Opera and Safari
        xhr.open(method, url, true);

    } else if (typeof XDomainRequest != "undefined") {
        // XDomainRequest对象,兼容IE
        xhr = new XDomainRequest():
        xhr.open(method, url);

    } else {
        xhr = null;

    }
    return xhr;
}

var xhr = get_CORS_XHR('GET', 'http://example.com');
  1. 事件处理

XMLHttpRequest2对象新增了许多事件类型,原先的对象只支持onreadystatechange,新增事件有: (*星号代表IE不支持)

  • onloadstart*(请求开始发送)
  • onprogress(加载和发送数据中)
  • onabort*(实例abort方法被调用)
  • onerror(请求失败)
  • onload(请求成功)
  • ontimeout(请求超时)
  • onloadend(请求完成,不管失败还是结束)
// 通常我们处理最多的就是
xhr.onload = function() {
    var responseText = xhr.responseText;
    console.log(responseText);
    // ...
};

xhr.onerror = function() {...};
  1. withCredentials & 发送

默认情况下,标准的CORS请求是不会发送任何cookie信息的。如果你想让请求带上对应域的cookies信息(需要server支持),那么你需要:

xhr.withCredentials = true;
// 响应报文头部加上 Access-Control-Allow-Credentials: true

// handlers ...
xhr.send();

Server跨域请求处理支持

请求分类

可以给跨域请求分个类:

  1. 简单请求

符合下列要求的请求可以说是简单请求:

- HTTP Method
    - HEAD
    - GET
    - POST
- HTTP Headers
    - Accept
    - Accept-Language
    - Content-Language
    - Last-Event-ID
    - Content-Type
        - application/x-www-form-urlencoded
        - multipart/form-data
        - text/plain
  1. 不太简单的请求(A term by Monsur Hossain)

不符合(1)中的条件的请求

浏览器如Chrome, Firefox等会在不太简单的CORS请求发送前,为安全性考虑先发送一条”preflighted”OPTIONS请求

服务器端的处理根据请求的复杂程度处理方式有所不同。

处理简单请求

让我们举个栗子,CORS指定头部为粗体: Javascript:

var url = 'http://api.alice.com/cors';
var xhr = get_CORS_XHR('GET', url);
xhr.send();

HTTP 请求:

GET /cors HTTP/1.1
Origin: http://api.bob.com
Host: api.alice.com
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0...

值得注意的是CORS请求中必定包含Origin头部,但是包含此头部不一定意味着这个请求就是CORS请求。一个成功的跨域请求的响应报文可以是:

Access-Control-Allow-Origin: http://api.bob.com
Access-Control-Allow-Credentials: true
Access-Control-Expose-Headers: FooBar
Content-Type: text/html; charset=utf-8

所有CORS相关的头部均以Access-Control-开头,下面是关于各个头部的细节:

  • Access-Control-Allow-Origin(required) 此头部必须添加到响应报文中 ,不然缺省值会导致CORS请求失败。你可以设置*值让所有站点都可以访问你的数据,但最好还是控制一下
  • Access-Control-Allow-Credentials(optional) 设置此头部的值为true,如果你想要请求附带cookies。与上文提到的withCredentials属性协作。若此头部值为true而withCredentials属性为false,会导致请求失败,反之亦然
  • Access-Control-Allow-Expose-Headers(optional) XMLHttpRequest2对象存在getResponseHeader方法,允许访问一些简单的响应头部如:Content-Type,Cache-Control等等。如果想暴露一些特殊的头部,可以在此头部的值设置以逗号分隔的头部名称
处理不太简单的请求

如上文所说,处理不太简单的请求时,浏览器会先发出一次preflighted的请求,得到服务器允许后才执行真正的跨域请求,preflighted请求的结果会被缓存,多条请求同一服务器的跨域请求只会发送一次preflighted请求。举个栗子: Javascript:

var url = 'http://api.alice.com/cors';
var xhr = createCORSRequest('PUT', url);
xhr.setRequestHeader(
    'X-Custom-Header', 'value');
xhr.send();

Preflight请求:

OPTIONS /cors HTTP/1.1
Origin: http://api.bob.com
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: X-Custom-Header
Host: api.alice.com
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0...
  • Access-Control-Request-Method 真实的请求方法,在此为PUT。所有的Preflight请求都应该包含此头部
  • Access-Control-Request-Headers 值是以逗号分隔的头部名称,代表请求附带的其余头部

Preflight响应:

Access-Control-Allow-Origin: http://api.bob.com
Access-Control-Allow-Methods: GET, POST, PUT
Access-Control-Allow-Headers: X-Custom-Header
Content-Type: text/html; charset=utf-8
  • Access-Control-Allow-Origin(同上文)
  • Access-Control-Allow-Credentials(同上文)
  • Access-Control-Allow-Methods(required) 允许跨域请求的请求类型,以逗号分隔。由于preflight响应可能被缓存,所以此头部设置会有所帮助
  • Access-Control-Allow-Headers 当请求中有Access-Control-Request-Headers头部时,此响应头说明服务器支持的头部,以逗号分隔
  • Access-Control-Max-Age(required) 指定preflight响应可以被缓存的时间,单位秒

真实的请求跟响应就可以正常发送接收了。如果服务器对preflight请求直接返回HTTP 200,不包含任何CORS指定的头部,那么这个跨域请求就会失败,触发onerror事件。控制台中会输出类似一下的报错信息:

XMLHttpRequest cannot load http://api.alice.com. Origin http://api.bob.com is not allowed by Access-Control-Allow-Origin.

Server处理流程图

注: 最好在服务器端的Access-Control-Allow-Methods头部加上OPTIONS方法

Access-Control-Allow-Methods: GET,POST,OPTIONS
Access-Control-Allow-Credentials: true
Access-Control-Allow-Origin: http://qunedu.oa.com

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏云计算教程系列

如何在Debian 9中为Apache创建自签名SSL证书

TLS或传输层安全性及其前身SSL(代表安全套接字层)是用于将正常流量包装在受保护的加密包装中的Web协议。

792
来自专栏吴裕超

为什么会有OPTIONS请求

在做项目时,很多时候发送一个post请求,是先发送一个option请求,然后再发送post请求,一直这么用之前也没有仔细思考,今天有时间,好好了解一下为什么会多...

1.8K11
来自专栏后端之路

单点登录cookie跨域二三事之withCredentials

背景 随着前后端项目开发的流行现在前后端跨域非常常见。 典型案例 后端SpringBoot 端口8079 前端VueJs 端口8075 存在如下场景在8075需...

2269
来自专栏超然的博客

JSONP && CORS

  前天面试被问到了跨域的问题,自我感觉回答的并不理想,下面我就分享一下整理后的总结分享给大家

1022
来自专栏阮一峰的网络日志

跨域资源共享 CORS 详解

CORS是一个W3C标准,全称是"跨域资源共享"(Cross-origin resource sharing)。 它允许浏览器向跨源服务器,发出XMLHttpR...

2907
来自专栏前端学习心得

跨域几种方式

30711
来自专栏对角另一面

简单设置,解决使用webpack前后端跨域发送cookie的问题

最近用vue来做项目,用webpack来做前端自动化构建。webpack-dev-server会在本地搭建一个服务器,在和后端调试的时候,就会涉及到跨域的问题。...

3230
来自专栏云计算教程系列

如何在Ubuntu 16.04中为Apache创建自签名SSL证书

TLS或传输层安全性及其前身SSL(代表安全套接字层)是用于将正常流量包装在受保护的加密包装中的Web协议。

1230
来自专栏前端布道

浏览器同源策略及跨域的解决方法

同源策略(Same origin policy)是一种约定,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,则浏览器的正常功能可能都会受到影响。可以说 ...

852
来自专栏meteorzx

express模拟接口

Failed to load http://192.168.xxx.xxx:9092/api/user/query?id=user123&name=%E6%B5...

4556

扫码关注云+社区