使用nginx做反向代理出现这种报错,虽然不影响站点正常显示,对于完美主义者是绝对不会妥协的。
环境还原:域外html集成的js要访问Nginx反向代理的一个站点。同事搭建,具体HTTP方法和header 我也没问,想着就把以前其他同事配置过的跨域的一段参数拷贝过来就行了,拷贝的具体参数如下
location /crosstest/web/ { add_header Access-Control-Allow-Origin: * ; add_header Access-Control-Allow-Credentials true; add_header Access-Control-Allow-Methods GET,PUT,POST,DELETE,OPTIONS; add_header Access-Control-Allow-Headers Content-Type,* ; proxy_pass https://webserver/web; }
测试过程中发现不行,浏览器会报错如下
Ensure preflight responses are valid和Ensure CORS response header values are valid
通过详情分析,大致意思是说从1.1.1.1 地址向2.2.2.2 发起的请求被跨域策略拦截了。因为请求的资源上不存在 ‘Access-Control-Allow-Origin’ 这个header,导致preflight request 失败。为了搞清楚客户端(源地址端)在访问Nginx的时候具体发生了啥,找同事要了前端的代码,放到本地 localhost:8080 里,自己研究下这个过程到底发生了啥。
从Fidder 抓取的请求看,客户端仅发送了一个OPTIONS 方法的请求,被服务器403状态码给拒绝了,查阅了有关OPTIONS方法和预检请求的博客和文档,梳理了大概关系
HTTP 请求分为简单请求 与 复杂请求,两种请求的区别主要在于简单请求不会触发CORS预检请求,而复杂请求会触发CORS预检请求
满足简单请求的条件(两个条件需要都满足)
方法为 GET、HEAD、POST 之一
无自定义请求头,且Content-Type 为 text/plain, mutipart/form-data application/x-www-form-urlencoded 之一
不满足简单请求的一切请求都是复杂请求
预检请求(一般是浏览器自动发起的OPTIONS方法的请求) 中Access-Control-Request-Method 字段告诉服务器实际请求会使用的HTTP方法;Access-Control-Request-Headers 字段告知服务器实际情况所携带的自定义首部字段。服务器基于预检请求获得的信息来判断,是否接受接下来的实际请求。服务器端返回的Access-Control-Allow-Methods 字段 将服务器允许的请求方法告诉客户端。该首部字段与Allow类似,但只能用户设计到CORS的场景中。
到这里,产生了几个疑问
为什么要发起预检请求 ?
《关于preflight request》 解释的比较清楚,目前浏览器限制跨域的方式主要有两种
浏览器限制发起跨域请求
跨域请求可以正常发起,但是返回的结果被浏览器拦截
一般浏览器都是采用第二种方式限制跨域请求,也就是说请求已经到达了服务器,如果是复杂请求,对服务器数据库的数据进行了操作,但返回给浏览器的结果却被拦截,被识别为一次失败的请求,这时候可能对数据库里数据已经产生了影响。为了防止这种情况发生,这种可能对服务器数据产生操作的HTTP请求,浏览器必须先试用OPTIONS 方法发起预检请求,从而获知服务器是否允许该跨域请求。
什么情况下会视为OPTIONS 预检请求通过?
带着疑问继续查看相关博客,看到很多人都是通过拦截OPTIONS请求,并设置相应的返回码来处理预检请求。
location /crosstest/web/ { add_header Access-Control-Allow-Origin: * ; add_header Access-Control-Allow-Credentials true; add_header Access-Control-Allow-Methods GET,PUT,POST,DELETE,OPTIONS; add_header Access-Control-Allow-Headers Content-Type,* ; proxy_pass https://webserver/web; if ($request_method = "OPTIONS"){ return 200; } }
经过测试,确实可行。
经过处理后不在报错
总结一下预检请求通过的条件
OPTIONS 方法获取的Response header 字段中,有Access-Control-* 的字段。表示服务器告知浏览器服务端支持跨域访问的方法和header 字段等。
复杂请求中的request method、request header、Origin等满足第1点中服务端告知给客户端的服务器端接受的条件。
当前两点都满足的时候,浏览器会才会发起跨域请求,否则浏览器直接拦截跨域请求,该请求不会走到服务器端。
其他补充
4. Access-Control-Max-Age 可以指定将预检请求的结果缓存多长时间,在这个时间范围内就不用再重复发起预检请求了
5. Access-Control-Allow-Credentials true 是否允许在跨域请求中传递cookie ,默认false
6. Access-Control-Allow-Origin 要根据实际允许跨域的origin填写,* 表示允许所有origin 跨域访问