最近上线了一个代理系统,通过nginx代理第三方应用来打通不同区域之间的防火墙限制,从而实现访问策略的一些业务。
期间在系统代理客户某个应用的时候遇到了跨域问题,由于自己的惯性思维的逻辑,导致花费了整整一天的时间才解决,而且还是同事协助完成,所以特此记录,用以警醒!
客户环境:
应用服务器:nginx部署实现负载均衡
应用涉及的ws服务器:nginx部署实现负载均衡
本地环境:
代理系统:通过nginx实现代理
具体操作:
在代理系统分别添加客户应用与ws服务器,通过nginx.conf配置了两个server块来实现。
结果在浏览器访问客户应用服务器的时候,客户应用通过ajax发送的ws请求全都抛出跨域异常(XMLHttpRequest cannot load ''. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin '' is therefore not allowed access.)。
跨域访问是浏览器的一种限制, 原因是为了安全问题;
浏览器通过同源策略来实现跨域限制,同源策略是指域名、协议、端口相同才是同一个源;
比如a页面想获取b页面资源,如果a、b页面的协议、域名、端口、子域名不同,所进行的访问行动都是跨域的。
在遇到跨域问题当时第一反应就是同源策略,想到代理系统配置两个server块来代理,而两个server_name的域名就产生了同源问题,
所以围绕着怎么把客户应用与ws服务器通过一个server块来实现花费了大把时间,关键在于客户ajax请求的ws是全路径,如http://aaa:8080/ws,这在本地发出ajax请求是直接发送给aaa域名的,而客户应用域名又为http://xxx:8080/app,
除非客户的ws请求改成相对路径,如/ws,否则就会出现同源问题;
用户是不可控的,所以一个server块实现的方案根本不可行!
纠结半天后退而求其次,想通过在代理的ws服务器设置http头(Access-Control-Allow-Origin)来解决当前客户需求,这样不足之处也是因为不能控制用户是否已经配置了该http头,如果客户添加了,那么我们再次设置的话是有问题的,经过测试,用户的ws服务器确实设置了Access-Control-Allow-Origin,所以最终会抛出异常(XMLHttpRequestcannot load. The 'Access-Control-Allow-Origin' header contains multiple values'*, *', but only one is allowed. Origin '' is therefore notallowed access);
在这里设置Access-Control-Allow-Origin花费了半天时间,因为没设置就会出现最初的跨域异常,设置了相当于两层代理都添加了这个http头信息而导致multiple values'*。
CORS是一个W3C标准,全称是”跨域资源共享”(Cross-originresource sharing)。
它允许浏览器向跨源服务器,发出XMLHttpRequest请求,从而克服了AJAX只能同源使用的限制。
整个CORS通信过程,都是浏览器自动完成,不需要用户参与。对于开发者来说,CORS通信与同源的AJAX通信没有差别,代码完全一样。浏览器一旦发现AJAX请求跨源,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,但用户不会有感觉。
因此,实现CORS通信的关键是服务器。只要服务器实现了CORS接口 ,就可以跨源通信。
在服务器响应客户端的时候,带上Access-Control-Allow-Origin头信息(这个header就是让服务器支持CORS的)。
Access-Control-Allow-Origin:http://kbiao.me 表明它允许”http://kbiao.me“发起跨域请求 Access-Control-Max-Age:3628800 表明在3628800秒内,不需要再发送预检验请求,可以缓存该结果 Access-Control-Allow-Methods:GET,PUT, DELETE 表明它允许GET、PUT、DELETE的外域请求 Access-Control-Allow-Headers:content-type 表明它允许跨域请求包含content-type头
jsonp 需要目标服务器配合一个callback函数。
iframe 需要目标服务器响应window.name。
CORS 需要服务器设置header :Access-Control-Allow-Origin。
nginx反向代理 不用目标服务器配合,支持所有浏览器。
建议使用nginx。
nginx代理默认会把header中参数的 "_" 下划线去掉,所以后台服务器后就获取不到带"_"线的参数名。
underscores_in_headers on; #该属性默认为off,表示如果header name中包含下划线,则忽略掉。
两层代理产生的异常后,发现根本就不是跨域问题,最后全面盘查才发现ws的请求虽然都是200,不过响应的数据都是空的,
原因是ws请求时没有携带参数,而真相就在这里,客户请求的参数包含了"_",nginx默认忽略。
把之前的猜测推倒重来,在代理的两个server块加上underscores_in_headers on,问题至此解决。