写点什么

记录我开发工作中遇到 HTTP 跨域和 OPTION 请求的一个坑

作者:Jerry Wang
  • 2021 年 12 月 15 日
  • 本文字数:2039 字

    阅读完需:约 7 分钟

记录我开发工作中遇到HTTP跨域和OPTION请求的一个坑

我通过这篇文章把今天工作中遇到的 HTTP 跨域和 OPTION 请求的一个坑记录下来。


场景是我需要在部署在域名 a 的 Web 应用里用 JavaScript 去消费一个部署在域名 b 的服务器上的服务。域名 b 上的服务也是我开发的,因此我将域名 a 加到了该服务的 HTTP 响应结构的头文件里,这样就允许了域名 a 上的 JavaScript 代码用 AJAX 访问域名 b 的服务。



域名 b 上的服务是一个 Servlet,允许域名 a 跨域访问的代码就一行:


protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 做业务逻辑
response.setHeader("Access-Control-Allow-Origin", "域名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 了一下,发现一些朋友遇到同样的问题:


  1. 如何解决出现 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


这位朋友的解决方案:


response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Allow-Credentials", "true");
response.setHeader("Access-Control-Allow-Methods", "*");
response.setHeader("Access-Control-Allow-Headers", "Content-Type,Access-Token");
response.setHeader("Access-Control-Expose-Headers", "*");
if (request.getMethod().equals("OPTIONS")) {
HttpUtil.setResponse(response, HttpStatus.OK.value(), null);
return;
}
复制代码



但我试过,在我的场景下还是不工作,因为我的例子里,服务器已经针对 OPTIONS 请求返回 HTTP 200 的状态码了。


2. 这个 Stackoverflow 的帖子里,很多朋友都提供了自己的解决方案。


https://stackoverflow.com/questions/42061727/cors-error-request-header-field-authorization-is-not-allowed-by-access-control/42061962


我一一试过,在我的场景里都不能工作。


于是我查询了 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 认证方式,这种方式不会造成该请求成为一个”需要预检的请求“,所以最后跨域成功了。



var formData = new FormData();
formData.append('sap-client', "001");
formData.append('sap-user', "用户名");
formData.append('sap-password', "用户密码");
var request = new XMLHttpRequest();
request.open("POST", "域名b的url",false);
request.send(formData);
alert("response: " + request.responseText);
复制代码



希望我的这个踩坑经历对大家有点帮助。


要获取更多 Jerry 的原创技术文章,请关注公众号"汪子熙"。

发布于: 14 小时前阅读数: 6
用户头像

Jerry Wang

关注

个人微信公众号:汪子熙 2017.12.03 加入

SAP成都研究院开发专家,SAP社区导师,SAP中国技术大使。

评论

发布
暂无评论
记录我开发工作中遇到HTTP跨域和OPTION请求的一个坑