Fork me on GitHub

简单跨域请求和复杂跨域请求的区别

前言

  • 跨域是因为浏览器有同源策略的限制,一段脚本只能读取来自同一来源的窗口和文档的属性,只要来源的协议域名端口有任何一个不同,都被当作是不同的域,网站间的数据请求与传输便构成了跨域调用,因此便会受到同源策略的限制。

CORS

  • 一个域是由协议(schema)域名(host)端口(port)三者共同组成,与路径无关
  • 跨域资源共享(Cross-Origin Resource Sharing, CORS)是为解决Ajax技术难实现跨域问题而提出的一个规范,这个规范试着从根本上解决安全的跨域资源共享问题。在此之前,解决此类问题的途径往往是服务器代理JSONP等,治标不治本。目前基本所有浏览器都已经支持该规范。
  • CORS是通过客户端 + 服务端协作声明的方式来确保请求安全的。
  • CORS约定服务器端浏览器在HTTP协议之上,通过一些额外HTTP头部信息,进行跨域资源共享的协商。服务器端和浏览器都必需遵循规范中的要求。
    • 服务端会在HTTP请求头中增加一系列HTTP请求参数(例如Access-Control-Allow-Origin等),来限制哪些域的请求和声明哪些请求类型可以接受。
    • 客户端在发起请求时必须声明自己的源(Orgin),否则服务器将不予处理,如果客户端不作声明,请求甚至会被浏览器直接拦截都到不了服务端。(对于支持CORS的浏览器,请求头会自动添加Origin,值为当前host)
    • 服务端收到HTTP请求后会进行域的比较,只有同域的请求才会处理。

CORS中HTTP请求的分类

  • 最近在使用CORS的时候,由于需要使用PUTDELETE等请求方法,发现原来CORS的规范定义远不止这些
  • CORS把HTTP请求分成两类,不同类别按不同的策略进行跨域资源共享协商,这两类请求分别是:
    • 简单跨域请求
    • 复杂跨域请求,即带预检(Preflighted)的跨域请求
简单跨域请求
  • 当HTTP请求出现以下两种情况时,浏览器认为是简单跨域请求
    1). 请求方法是GETHEAD或者POST,并且当请求方法是POST时,Content-Type必须是application/x-www-form-urlencoded, multipart/form-data或着text/plain中的一个值。
    2). 请求中没有自定义HTTP头部
  • 对于简单跨域请求,浏览器要做的就是在HTTP请求中添加Origin Header,将JavaScript脚本所在域填充进去,向其他域的服务器请求资源。服务器端收到一个简单跨域请求后,根据资源权限配置,在响应头中添加Access-Control-Allow-Origin Header。浏览器收到响应后,查看Access-Control-Allow-Origin Header,如果当前域已经得到授权,则将结果返回给JavaScript否则浏览器忽略此次响应
带预检(Preflighted)的跨域请求
  • 当HTTP请求出现以下两种情况时,浏览器认为是带预检(Preflighted)的跨域请求
    1). 除GETHEADPOST(only with application/x-www-form-urlencoded, multipart/form-data, text/plain Content-Type)以外的其他HTTP方法
    2). 请求中出现自定义HTTP头部
  • “预检”其实相当于做检查,检查如果通过则允许传输数据,检查不通过则不再发送真正想要发送的消息。
浏览器端
  • 带预检(Preflighted)的跨域请求需要浏览器发送真实HTTP请求之前先发送一个OPTIONS的预检请求,检测服务器端是否支持真实请求进行跨域资源访问。
  • 真实请求的信息在OPTIONS请求中通过Access-Control-Request-Method HeaderAccess-Control-Request-Headers Header描述。
  • 此外,与简单跨域请求一样,浏览器也会添加Origin Header
  • 带预检(Preflighted)的跨域请求第一次请求,header中不会带自定义的header头信息。表示真实请求的自定义Header列表的是Access-Control-Request-HeadersHTTP请求头
服务器端
  • 服务器端接到预检请求后,根据资源权限配置,在响应头中放入Access-Control-Allow-Origin HeaderAccess-Control-Allow-MethodsAccess-Control-Allow-Headers Header,分别表示允许跨域资源请求的请求方法请求头
  • 此外,服务器端还可以加入Access-Control-Max-Age Header,允许浏览器在指定时间内,无需再发送预检请求进行协商,直接用本次协商结果即可。
浏览器端
  • 浏览器根据OPTIONS请求返回的结果来决定是否继续发送真实的请求进行跨域资源访问。这个过程对真实请求的调用者来说是透明的。

CORS协议规定的HTTP头

  • XMLHttpRequest支持通过withCredentials属性实现在跨域请求携带身份信息(Credential,例如Cookie或者HTTP认证信息)。
  • 跨域请求默认不会携带Cookie信息,如果需要携带,请配置下述参数:

    1
    2
    3
    4
    5
    6
    7
    // 服务端
    "Access-Control-Allow-Credentials": true

    // 客户端需要设置withCredentials
    // Ajax设置
    var xhr = new XMLHttpRequest();
    xhr.withCredentials = true;
  • 浏览器将携带Cookie Header的请求发送到服务器端后,如果服务器没有响应Access-Control-Allow-Credentials Header,那么浏览器会忽略掉这次响应。

  • 这里讨论的HTTP请求是指由Ajax XMLHttpRequest对象发起的,所有的CORS HTTP请求头都可由浏览器填充,无需在XMLHttpRequest对象中设置
  • 以下是CORS协议规定的HTTP头,用来进行浏览器发起跨域资源请求时进行协商:
    1. Origin。HTTP请求头,任何涉及CORS的请求都必需携带。
    2. Access-Control-Request-Method。HTTP请求头,在带预检(Preflighted)的跨域请求中用来表示真实请求的方法
    3. Access-Control-Request-Headers。HTTP请求头,在带预检(Preflighted)的跨域请求中用来表示真实请求的自定义Header列表
    4. Access-Control-Allow-Origin。HTTP响应头,指定服务器端允许进行跨域资源访问的来源域。可以用通配符*表示允许任何域的JavaScript访问资源,但是在响应一个携带身份信息(Credential)的HTTP请求时Access-Control-Allow-Origin必须指定具体的域,不能用通配符
    5. Access-Control-Allow-Methods。HTTP响应头,指定服务器允许进行跨域资源访问的请求方法列表,一般用在响应预检请求上。
    6. Access-Control-Allow-Headers。HTTP响应头,指定服务器允许进行跨域资源访问的请求头列表,一般用在响应预检请求上。
    7. Access-Control-Max-Age。HTTP响应头,用在响应预检请求上,表示本次预检响应的有效时间在此时间内,浏览器都可以根据此次协商结果决定是否有必要直接发送真实请求,而无需再次发送预检请求
    8. Access-Control-Allow-Credentials。HTTP响应头,凡是浏览器请求中携带了身份信息,而响应头中没有返回Access-Control-Allow-Credentials: true的,浏览器都会忽略此次响应

总结

  • 同时满足以下两个条件时,则是简单请求,否则为复杂请求:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    1、请求方式:HEAD、GET、POST
    2、请求头信息不超出以下几种字段:
    Accept
    Accept-Language
    Content-Language
    Last-Event-ID
    Content-Type 对应的值是以下三个中的任意一个:
    application/x-www-form-urlencoded
    multipart/form-data
    text/plain
  • 简单请求和复杂请求(带预检(Preflighted)的跨域请求)的区别:

    • 简单请求:一次请求。
    • 复杂请求:两次请求,它是对服务器有特殊要求的请求,比如请求方法是PUTDELETE,或者Content-Type字段的类型是application/json。复杂请求在发送数据之前会先发一次请求用于做“预检”,只有“预检”通过后才再发送第二次请求。
  • 预检请求为OPTIONS请求,用于向服务器请求权限信息的。预检请求被成功响应后,才会发出真实请求,携带真实数据。
------ 本文结束 ------