跨域 POST 请求之 CROS

编辑于2017年12月15日

跨域资源共享(Cross-origin resource sharing,简称 "CROS")是一个新的 W3C 标准,它允许浏览器跨域请求服务器上的资源,就像请求同源的服务器资源一样。对于跨域问题常见的解决方法就是使用 JSONP,但是 JSONP 是不支持 POST 请求的,在 jQeury 中即使设置为 POST 也依旧会以 GET 请求提交。当不需要兼容低版本浏览器时,相较于 iframe、proxy 等,CROS 不失为一个简单、优雅的解决方案。

前言

最近在项目中需要跨域提交 POST 请求,使用的是 CROS 作为解决方案。之前对于 CROS 有过一些了解,但没有具体的尝试使用,只是觉得 CROS 机制并不复杂:无外乎就是提供了一种跨域请求资源时,如果服务器同意就允许此次请求的机制。但是,这次在具体使用时却遇到了麻烦。

基本使用

CROS 使用起来十分简单,前端不需要做任何处理,只需要服务器在响应请求时,在响应头中设置几个特殊的字段即可,具体是哪些字段根据请求类的型不同而不同。

利用 CROS 跨域请求被分为两种:一种是不需要进行预检的简单请求,另一种是需要进行预检的非简单请求。简单请求和非简单请求主要通过请求类型和请求头进行决定,如果请求满足下面的条件就是简单请求,否则就是非简单请求。

  1. 请求方法是 HEAD、GET 或者 POST 方法。
  2. 请求头中只包含这些头:Accept、Accept-Language、Content-Language、Last-Event-ID 和 Content-Type,其中 Content-Type 的值只能是 application/x-www-form-urlencoded、multipart/form-data 或者 text/plain。

简单请求

对于简单请求,浏览器在发出请求时会在请求头中添加一个 Origin 字段用来说明本次请求来自哪个源,服务器可以根据这个字段来决定是否允许此次访问。

如果服务器同意此次请求,那么在响应头中会包含 Access-Control-Allow-Origin 字段,浏览器发现响应头中包含该字段,就知道这个请求是被允许的。相反地,如果服务器拒绝了本次请求,那么在响应头中就不会包含该字段,这时浏览器就会抛出一个错误。

非简单请求

非简单请求相较于简单请求而言最大的区别就是有一个预检的过程,即浏览器在发出正式的请求之前会先发出一个 OPTIONS 请求,该请求的作用是先询问服务器当前域是否可以访问以及是否允许此类方法和其他的请求头字段,当服务器中的响应头中包含允许此请求的字段,浏览器才会发出正式的请求。

实际应用

在这次实际应用过程中,我起初并不清楚发送 POST 请求之前有一个预检的过程,我简单的以为只需要服务端在返回时添加对应的头即可,但是 POST 请求一直不成功,如果换成 GET 请求则可以正常请求,这一度让我百思不得其解。

浏览器上报的错误就是没有跨域的权限,但是其实在那之前还有一个 401。我注意到了这一点,但是我以为这是因为没有权限所以才返回的 401。但是,其实是因为服务端有鉴权的过程,而 OPTIONS 请求鉴权没有通过,所以服务端直接返回了 401,没有执行到设置响应头的处理,这才导致一直请求不成功。

后面,将服务端中对于 OPTIONS 请求的鉴权去掉就可以成功跨域了。

总结

这次问题其实是一个很小的问题,在解决的过程中之所以花了过多的时间就是因为我想当然了,明明就不是很清楚,自以为 401 是因为没有跨域权限导致的。所以,以后在遇到问题时还是不能太想当然,可以进行适当的猜测,但是一定要去验证猜测的正确性。如果能又快又对的定位问题的原因,可以节省大量摸石头过河的过程。🤡🤡

希望对您能有帮助,打赏随意