记录我开发工作中遇到 HTTP 跨域和 OPTION 请求的一个坑
我通过这篇文章把今天工作中遇到的 HTTP 跨域和 OPTION 请求的一个坑记录下来。
场景是我需要在部署在域名 a 的 Web 应用里用 JavaScript 去消费一个部署在域名 b 的服务器上的服务。域名 b 上的服务也是我开发的,因此我将域名 a 加到了该服务的 HTTP 响应结构的头文件里,这样就允许了域名 a 上的 JavaScript 代码用 AJAX 访问域名 b 的服务。
域名 b 上的服务是一个 Servlet,允许域名 a 跨域访问的代码就一行:
我在域名 a 的 Web 应用里用 AJAX 发起服务请求:
执行后,发现并没有显示 200 的弹出窗口。
错误消息:Request header field Authorization is not allowed by Access-Control-Allow-Headers in preflight response.
观察 Chrome 开发者工具,发现其实域名 b 的服务已经成功执行了,确实返回了 200 的 Status code,
而且我已经从 Chrome 开发者工具里观察到浏览器已经成功接到域名 b 发送回来的请求了。
那这个错误是什么鬼呢?根据错误消息“Request header field Authorization is not allowed by Access-Control-Allow-Headers in preflight response” Google 了一下,发现一些朋友遇到同样的问题:
如何解决出现 AXIOS 的 Request header field Content-Type is not allowed by Access-Control-Allow-Headers in preflight response.
网页地址: https://www.cnblogs.com/caimuqing/p/6733405.html
这位朋友的解决方案:
但我试过,在我的场景下还是不工作,因为我的例子里,服务器已经针对 OPTIONS 请求返回 HTTP 200 的状态码了。
2. 这个 Stackoverflow 的帖子里,很多朋友都提供了自己的解决方案。
我一一试过,在我的场景里都不能工作。
于是我查询了 Mozilla 的一篇文档:HTTP 访问控制(CORS)
https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Access_control_CORS
里面谈到了,在某些情况下,浏览器在发起“需要预检的请求”之前,必须首先发起一个“预检请求(Preflight)”到服务器,以探测服务器是否允许这个实际请求。"预检请求"机制的使用,是为了避免跨域请求对服务器的用户数据产生未预期的影响。
那么哪些请求算作“需要预检的请求”呢?Mozilla 的这篇文档定义得很清楚:
当请求满足下述任一条件时,即应首先发送预检请求:
使用了下面任一 HTTP 方法:
PUT
DELETE
CONNECT
OPTIONS
TRACE
PATCH
人为设置了对 CORS 安全的首部字段集合之外的其他首部字段。该集合为:
Accept
Accept-Language
Content-Language
Content-Type (but note the additional requirements below)
DPR
Downlink
Save-Data
Viewport-Width
Width
Content-Type 的值不属于下列之一:
application/x-www-form-urlencoded
multipart/form-data
text/plain
我再检查我的代码,因为我在 HTTP 请求里用 xhr.setRequestHeader("Authorization", "用户名:密码的 base64 编码" )添加了用于 Basic Authentication 的头部,因此迫使该请求成为了“需要预检的请求”,所以才有了 OPTION 请求的发送。
现在我将其注释掉:
这次遇到了 401 Unauthorized 错误了:
然而没有预检请求 OPTION 发出来了,请求类型变成了我期望的 POST 方式了。
但是现在就陷入了一个矛盾的境地:如果在请求头部加上 Basic Authentication 的信息,会遇到错误消息“Request header field Authorization is not allowed by Access-Control-Allow-Headers in preflight response.”。如果去掉,虽然避免了预检请求,但是又遇到 401 Unauthorized 错误了。
于是,我换了一种认证方式,终于成功实现了期望的跨域请求,在我域名 a 的前端应用里打印出了来自于域名 b 的服务的响应。
我使用了 form 认证方式,这种方式不会造成该请求成为一个”需要预检的请求“,所以最后跨域成功了。
希望我的这个踩坑经历对大家有点帮助。
要获取更多 Jerry 的原创技术文章,请关注公众号"汪子熙"。
版权声明: 本文为 InfoQ 作者【Jerry Wang】的原创文章。
原文链接:【http://xie.infoq.cn/article/40fcf5f32a4e8b1b86006d56f】。文章转载请联系作者。
评论