目录
为什么要做这样的骚操作
如何成功的调用第三方服务接口
总结
说到跨域,一般都是前后端分离之后,前端域名和后端域名不一致,从而被浏览器的同源策略拦截所导致。正常来讲,随着前后端分离的大流行,对于跨域问题的解决,大家都比较熟悉了。如果有同学对一般跨域问题有疑惑或者不熟悉的,可以先看看基本的跨域问题处理。
按理说碰到跨域问题,作为一个后端老油条来说,应该是轻车驾熟的;然而,常在河边走,哪有不湿鞋的,这次就碰上了一个很奇葩的需求。
上头给下来的需求大概描述一下是这样:
我们给某个运营商做推广,推广他们的某种号卡;我方自己出对应的H5落地页,在信息流平台上进行推广,然后将客户填写的联系信息同步给运营商。但是呢,奇葩的点来了,运营商不愿意给我们接口文档,更别说安排人和我们对接了。那数据怎么进行同步?运营商说,简单啊,我给你一个你的专属渠道的官方H5页面,你把你的客户信息,通过这个页面进行提交。
运营商的意思其实也很清楚,就是,你们可以像一个憨憨一样,在这个页面上疯狂提交客户信息;也可以对我这个页面的接口请求进行分析,我也不需要给你们分配账号配置接口权限,只要你们分析清楚了接口字段,随便你们怎么调接口。但是接口文档嘛,you just think think then good
。
所以说,我们后面的操作,都是经过三方服务的默许的。一般情况下,不要随随便便就去搞别人接口,哈哈哈。
接到这个需求,我就和老大开玩笑说,那就在我们这边进件后,导出用户信息的excel给运营,让他们去填表单,让他们经常搞事情害我们加班。
要是运营当时在,估计他们的内心OS是这样的。
玩笑归玩笑,这个需求的最终落地,肯定还是要通过接口分析后,直接在页面调用他们的接口进行信息提交的。
接口分析,其实也就是在浏览器上F12查看网页相关信息,走页面上的流程,看每个流程调用的接口,然后分析里面的具体字段,同时解析一下前端对应的加密方法(不需要进行破解,因为是从明文到密文,所以只需要调用对应的js进行加密即可),这是一个费时的操作,但是一步一步做,还是能稳步推进的。而最终的难点其实还是在于*如何处理与第三方服务的跨域问题,成功调用他们的接口。
虽然操作很骚,但是还是要开搞,下面开始进入正题。
浏览器限制从脚本内发起的跨源Http请求,只能加载应用程序的同一个域下的Http资源,除非使用CORS头文件
,具体的请求头部字段,可以参考MDN的HTTP访问控制的译文。
从浏览器端看,跨域拦截的直接表现就是,对于复杂请求,会首先发送一个预请求Option
,在请求接口后,浏览器会获取响应头中的Access-Control-Allow-Origin
的值与当前的来源,即请求里面的Origin
进行判断是否一致,一致则代表在许可范围内,能够继续访问,否则不能进行访问。
所以说,我们最终要解决的其实还是怎么处理CORS
头文件。
从跨域问题处理中可以看到,如果通过后端服务处理,是以拦截器的方式进行处理,但是这里,因为是直接访问第三方服务,所以,请求走内部的拦截器不合适,单独开发新的处理方式,又没有太大的必要,于是我选择通过nginx
的方式进行跨域处理(nginx
作为一个高性能的Http和反响代理服务器,简直堪称神器,对这个神奇的各种使用可以参考我的nginx系列)。
Round 1 —— nginx添加header处理跨域
当决定使用ng处理之后,话不多说,我就在ng上配置了这么一段
location /yd {
if ($request_method = 'OPTIONS') {
add_header Access-Control-Allow-Origin $http_origin always;
add_header Access-Control-Allow-Credentials true always;
add_header Access-Control-Allow-Methods 'GET,POST,PUT,DELETE,OPTIONS' always;
add_header Access-Control-Allow-Headers 'Authorization,X-Requested-With,Content-Type,Origin,Accept' always;
add_header Access-Control-Max-Age 3600;
add_header Content-Length 0;
return 200;
}
add_header Access-Control-Allow-Origin $http_origin always;
add_header Access-Control-Allow-Credentials true always;
add_header Access-Control-Allow-Methods 'GET,POST,PUT,DELETE,OPTIONS' always;
add_header Access-Control-Allow-Headers 'Authorization,X-Requested-With,Content-Type,Origin,Accept' always;
proxy_pass http://...;
}
意思就是对每一个请求,设置它的响应头里面的信息允许发起请求的Origin
进行访问,以及设置了一些可以访问的方法,和对Option
请求的处理。作为常规跨域问题,到了这一步应该就可以打完收工了。放下撸起的袖子,准备收工时,惊喜来了。
这个是个什么鬼,为什么会有两份CORS
头文件,两份头文件,浏览器不认的哇。
心里暗骂一声,卧槽,他们自己已经配置了跨域设置了,这问题就变大发了啊,这就变成了如何突破第三方服务的跨域拦截,成功调用他们的接口。
Round 2 —— 把第三方返回的header搞掉
既然CORS
头文件只能有一份,那么我和第三方服务之间的关系就成了不是你挂掉,就是我饭碗丢掉的关系了。想想,还得攒钱取老婆,饭碗不能丢,那就只能跟第三方服务说一声对不起,还是把你的CORS
头搞掉吧。
但是问题是怎么把它搞掉了呢,这搞不好最后我就成了个嘴炮王者了呀。
静心一想,ng神器在手,还慌个啥,所以google一波后,我找到了ng神器的headers-more-nginx-module这个模块,这个模块是openresty提供的,这是openresty传送门。不知道openresty的可要好好去瞧瞧了,只能这么说,它让ng变得更好用,用途更广。
说回headers-more-nginx-module
,这个模块提供了more_set_headers
和more_set_input_headers
分别用于设置请求、响应头。也就是说,它不仅仅是添加,而是对已有的头字段进行修改,对没有的进行添加。不过这个模块不支持0.7.x以前的ng,但是目前ng最新版本已经到了1.16.x,所以大部分的ng都可以安装这个模块。
还等啥,直接把它安排上(此处默认ng已经安装完毕,此处是进行新增模块,另外Windows环境里已经下载好的exe,是不支持编译安装的,所以只能在Linux上搞)。
headers-more-nginx-module
模块# 切换到ng的下载包目录
cd /home/nginx/nginx-1.15.12
# 下载并解压(具体版本可在github自行选择)
wget https://github.com/openresty/headers-more-nginx-module/archive/v0.33.tar.gz
tar -zxvf v0.33.tar.gz
# 配置(prefix是ng的安装目录)
./configure --prefix=/usr/local/nginx --add-module=/home/nginx/nginx-1.15.12/headers-more-nginx-module-0.33
cd
# 编译 (切记切记没有 make install,否则会对整个ng重新进行安装)
make
# 备份现有安装目录下的启动命令
cp /home/nginx/nginx-1.15.12/sbin/nginx /home/nginx/nginx-1.15.12/sbin/nginx.bak
# 将添加了模块后的下载目录里面的启动命令覆盖安装路径里面的启动命令
cp -f /home/nginx/nginx-1.15.12/sbin/nginx /usr/local/nginx
# 重新启动
cd /usr/local/nginx
nginx -s reload
# 如果重启没生效,则可以先停止,再启动
nginx -s stop
./nginx
vi /usr/local/nginx/conf
======conf start=======
---变量说明(这几个需要代替为实际的值 )---
${host} 为原来需要访问的接口地址
${referer} 为原来请求请求的域名
---
location / {
proxy_pass ${host};
proxy_redirect off;
proxy_set_header Host ${host};
# 将请求头和来源修改为和第三方服务一样的值,跳过第三方的验证来源验证
proxy_set_header origin ${host};
proxy_set_header referer ${referer};
# 预请求直接返回
if ($request_method = 'OPTIONS') {
more_set_headers 'Access-Control-Allow-Origin *' always;
more_set_headers 'Access-Control-Allow-Credentials true' always;
more_set_headers 'content-type application/json' always;
more_set_headers' Access-Control-Allow-Methods GET,POST,PUT,DELETE,OPTIONS' always;
more_set_headers 'Access-Control-Allow-Headers content-type' always;
more_set_headers 'Access-Control-Max-Age 3600';
more_set_headers 'Content-Length 0';
return 200;
}
# 设置响应头
more_set_headers 'Access-Control-Allow-Origin *' always;
more_set_headers 'Access-Control-Allow-Credentials true' always;
more_set_headers 'content-type application/json' always;
more_set_headers 'Access-Control-Allow-Methods GET,POST,PUT,DELETE,OPTIONS' always;
more_set_headers 'Access-Control-Allow-Headers content-type' always;
}
======conf end=======
配置好,重启验证,结果如下,Nice。
但是为什么后续的接口请求没有携带Cookie呢,明明响应头里面已经设置Access-Control-Allow-Credentials
为true
了,浏览器会把相应内容返回了啊。其实这是因为对于附带身份凭证的请求,Access-Control-Allow-Origin
的值不能为*
,需要设置成具体的值。
需要做如下操作才能携带上cookie。
Access-Control-Allow-Origin
的值修改为请求的Origin
.axios.defaults.withCredentials = true
(vue语言)两回合搞定,打完收工,开始总结。
如何突破第三方服务的跨域拦截,调用三方服务接口最终的实现,其实还是先分析了浏览器的同源策略及解决方案,然后通过ng修改了请求中的请求头的CORS
文件。ng中内置的ngx_http_headers_module
模块,只能通过add_header
来添加请求头,不能进行修改。所以需要安装单独的headers-more-nginx-module
模块,该模块提供了more_set_headers
和more_set_input_headers
分别用于设置请求,响应头。
通过这两个命令,我们就能够自定义修改请求中的请求头和响应头,从而 骗过 浏览器的同源策略,实现在自己的前端页面,调用第三方服务的接口的需求。