跨域CORS小记

@fwon 2017-01-23 09:32:04发表于 fwon/blog

浏览器对XMLHttpRepeat或Fetch发起的请求都有同域的限制。

在调用跨域API的时候,用的最多的是jsonp的方式。

jsonp
jsonp的原理就是往页面添加一个<script>标签,通过在src连接后面添加callback=callbackName参数,并在页面定义callbackName方法。后端获取参数名后往页面输出callbackName(data); 即执行了页面定义好的回调函数,将数据返回给前端页面。
本质上这也是一种跨域请求,但是浏览器对这种直接引入链接的方式并没有限制。就像直接在页面上用<script>引入第三方js,css文件。

这种方式的缺点是,如果原先接口不是采用jsonp的方式,改动影响面较大。

CORS
CORS也可以解决跨域请求的问题,然而思路就不一样了。

CORS也需要后端配合,浏览器发起ajax请求时,会在HTTP请求头中携带Origin头,表示当前发起请求的域名。通过对比Origin和Request URL,即请求接口域名,浏览器自动判断该请求是否为跨域请求。

跨域ajax请求可以分为两种情况:简单请求和复杂请求。
简单请求包括:GET,HEAD请求,和Content-Type为application/x-www-form-urlencoded, multipart/form-data 或 text/plain 的POST请求。
复杂请求为:除了GET,HEAD,POST之外的其他请求,也包括Content-type为application-xml或者 text/xml的POST请求

当浏览器发起的请求为简单ajax请求时。服务端接收到请求后,会返回数据给浏览器。这时候浏览器会检查接口的返回头,判断是否有Access-Control-Allow-xxx相关的头部。如果Access-Control-Allow-Origin为*,或者白名单有发起请求的Origin。那么浏览器就会将数据返回给前端,否则的话请求将不会返回,浏览器直接在控制台报出跨域异常信息。

当浏览器发起的请求为复杂ajax请求时,浏览器首先会往后端发起一个OPTIONS请求,并提交字段Access-Control-Request-Method,询问服务器是否支持这种Type请求方式,比如Access-Control-Request-Method: POST。

服务端接收到请求后,会返回数据给浏览器。这时候浏览器会检测接口的返回头,判断Access-Control-Allow-Origin是否设置允许跨域,同时检查Access-Control-Request-Method是否包括请求的方式。通常还会返回Access-Control-Max-Age用于告知浏览器多长时间内对同样的请求免除发起OPTIONS预请求。剩下的处理方式跟简单请求一致。

CORS携带cookie
有些接口需要判断用户状态,在发起请求的时候要携带cookie。我们知道同域的请求都会自动带上cookie。对于跨域的ajax, XMLHttpRequest 方法中有个参数withCredentials, 通过设置参数为xhr.withCredentials = true,接口请求中也会自动带上cookie。当然这里面后端也要做点工作。

通过Set-Cookie头部,服务器能为浏览器设置cookie,可以同时支持发送多个Set-Cookie头设置多个cookie,并且通过path字段设置cookie的有效存放路径,浏览器发起的请求只有命中了path, 才能携带该cookie到服务器。 还有必须设置另外一个头部Access-Control-Allow-Credentials,当该值为true的时候,才允许往接口所在域名下发cookie。当ajax请求中的withCredentials为true时,浏览器会将cookie保存到域名下。而当前端请求中并没有设置withCredentials:true,即使后端返回了Access-Control-Allow-Credentials:true,浏览器也不会保存cookie。

所以在跨域请求的时候,往往要在第一次请求的时候保存cookie,后面请求的时候才能将此cookie携带给后端。

为了安全考虑,后端在设置Access-Control-Allow-Credentials:true的时候,不允许将Access-Control-Allow-Origin设置为*,只能够用添加白名单的方式

调试跨域问题的时候通常还要检查一下前端的提交方式正不正确,后端在获取值的时候怎么获取的。在没有明确的接口文档的时候,双方要确认好数据的提交方式,哪些数据是在Request Url里的,哪些数据是在data里的。才能避免跨域问题带来的调试陷阱。