关于浏览器跨域访问

生产上上线了展示能力输出功能,此功能将把内部的H5页面,在其他APP可以嵌入,涉及到跨域访问的问题
进行总结
参考文档:
跨域资源共享 CORS 详解
跨域的那些事儿

1.什么是跨域

别笑,之前我还真不知道
什么是跨域?
跨域,指的是浏览器不能执行其他网站的脚本。它是由浏览器的同源策略造成的,是浏览器施加的安全限制。

所谓同源是指,域名,协议,端口均相同,不明白没关系,举个栗子:
http://www.123.com/index.html 调用 http://www.123.com/server.php (非跨域)
http://www.123.com/index.html 调用 http://www.456.com/server.php (主域名不同:123/456,跨域)
http://abc.123.com/index.html 调用 http://def.123.com/server.php (子域名不同:abc/def,跨域)
http://www.123.com:8080/index.html 调用 http://www.123.com:8081/server.php (端口不同:8080/8081,跨域)
http://www.123.com/index.html 调用 https://www.123.com/server.php (协议不同:http/https,跨域)
请注意:localhost和127.0.0.1虽然都指向本机,但也属于跨域。
浏览器执行javascript脚本时,会检查这个脚本属于哪个页面,如果不是同源页面,就不会被执行。

2.浏览器的同源策略

同源策略又分为以下两种

  • DOM同源策略:禁止对不同源页面DOM进行操作。这里主要场景是iframe跨域的情况,不同域名的iframe是限制互相访问的。
    XmlHttpRequest同源策略:禁止使用XHR对象向不同源的服务器地址发起HTTP请求。
  • 只要协议、域名、端口有任何一个不同,都被当作是不同的域,之间的请求就是跨域操作。

3.为什么我们需要跨域限制

主要是出于安全的考虑
AJAX同源策略主要用来防止CSRF攻击。如果没有AJAX同源策略,相当危险,我们发起的每一次HTTP请求都会带上请求地址对应的cookie,那么可以做如下攻击:

  1. 用户登录了自己的银行页面 http://mybank.comhttp://mybank.com向用户的cookie中添加用户标识。
  2. 用户浏览了恶意页面 http://evil.com。执行了页面中的恶意AJAX请求代码。
  3. http://evil.comhttp://mybank.com发起AJAX HTTP请求,请求会默认把http://mybank.com对应cookie也同时发送过去。
  4. 银行页面从发送的cookie中提取用户标识,验证用户无误,response中返回请求数据。此时数据就泄露了。
  5. 而且由于Ajax在后台执行,用户无法感知这一过程。

DOM同源策略也一样,如果iframe之间可以跨域访问,可以这样攻击:

  1. 做一个假网站,里面用iframe嵌套一个银行网站 http://mybank.com
  2. 把iframe宽高啥的调整到页面全部,这样用户进来除了域名,别的部分和银行的网站没有任何差别。
  3. 这时如果用户输入账号密码,我们的主网站可以跨域访问到http://mybank.com的dom节点,就可以拿到用户的输入了,那么就完成了一次攻击。

4.如何做到合理的跨域访问---CORS

理论基础:CORS:”跨域资源共享”(Cross-origin resource sharing),这是一个W3C标准
CORS需要浏览器和服务器同时支持。目前,所有浏览器都支持该功能,IE浏览器不能低于IE10。
整个CORS通信过程,都是浏览器自动完成,不需要用户参与。对于开发者来说,CORS通信与同源的AJAX通信没有差别,代码完全一样。浏览器一旦发现AJAX请求跨源,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,但用户不会有感觉。
因此,实现CORS通信的关键是服务器。只要服务器实现了CORS接口,就可以跨源通信

  • CORS 机制
    浏览器将CORS请求分成两类:简单请求(simple request)和非简单请求(not-so-simple request)。
    具体可参考开头的阮一峰的参考文献

简要来讲,非简单请求多了一个预检的操作

4.1 预检会是一个OPTIONS的请求,例如

OPTIONS /cors HTTP/1.1
Origin: http://api.bob.com
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: X-Custom-Header
Host: api.alice.com
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0...`

4.2 服务器需要对预检请求进行回应

服务器收到"预检"请求以后,检查了OriginAccess-Control-Request-MethodAccess-Control-Request-Headers字段以后,确认允许跨源请求,就可以做出回应。

> HTTP/1.1 200 OK
> Date: Mon, 01 Dec 2008 01:15:39 GMT
> Server: Apache/2.0.61 (Unix)
> Access-Control-Allow-Origin: http://api.bob.com
> Access-Control-Allow-Methods: GET, POST, PUT
> Access-Control-Allow-Headers: X-Custom-Header
> Content-Type: text/html; charset=utf-8
> Content-Encoding: gzip
> Content-Length: 0
> Keep-Alive: timeout=2, max=100
> Connection: Keep-Alive
> Content-Type: text/plain

如果浏览器否定了"预检"请求,会返回一个正常的HTTP回应,但是没有任何CORS相关的头信息字段。这时,浏览器就会认定,服务器不同意预检请求,因此触发一个错误,被XMLHttpRequest对象的onerror回调函数捕获。控制台会打印出如下的报错信息。

XMLHttpRequest cannot load http://api.alice.com.
Origin http://api.bob.com is not allowed by Access-Control-Allow-Origin.

(1)Access-Control-Allow-Methods
该字段必需,它的值是逗号分隔的一个字符串,表明服务器支持的所有跨域请求的方法。注意,返回的是所有支持的方法,而不单是浏览器请求的那个方法。这是为了避免多次"预检"请求。

(2)Access-Control-Allow-Headers
如果浏览器请求包括Access-Control-Request-Headers字段,则Access-Control-Allow-Headers字段是必需的。它也是一个逗号分隔的字符串,表明服务器支持的所有头信息字段,不限于浏览器在"预检"中请求的字段。

(3)Access-Control-Allow-Credentials

该字段与简单请求时的含义相同。

(4)Access-Control-Max-Age

该字段可选,用来指定本次预检请求的有效期,单位为秒。上面结果中,有效期是20天(1728000秒),即允许缓存该条回应1728000秒(即20天),在此期间,不用发出另一条预检请求。

4.3 浏览器的正常请求和回应

一旦服务器通过了"预检"请求,以后每次浏览器正常的CORS请求,就都跟简单请求一样,会有一个Origin头信息字段。服务器的回应,也都会有一个Access-Control-Allow-Origin头信息字段。
下面是"预检"请求之后,浏览器的正常CORS请求。

PUT /cors HTTP/1.1
Origin: http://api.bob.com
Host: api.alice.com
X-Custom-Header: value
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0...

上面头信息的Origin字段是浏览器自动添加的。

下面是服务器正常的回应。

> Access-Control-Allow-Origin: http://api.bob.com
> Content-Type: text/html; charset=utf-8

特别注意!!!

上面头信息中,Access-Control-Allow-Origin字段是每次回应都必定包含的。

5 作为服务器开发者,我们到底怎么做的

5.1 我们需要做什么

可以从上文的流程中看到,服务器的开发者,需要做的时候分2步

  • 1.在预检请求中,添加信息,并返回200/204
    204是一个没有响应体的成功响应

  • 2.在后续的请求中,继续添加Access-Control的信息

5.2 实际操作

生产我们用Nginx作为反向代理,所以我们需要再Nginx进行处理

  • 一个普通的Nginx配置
server {
        listen       80; #监听80端口,可以改成其他端口
        server_name  localhost; # 当前服务的域名

        location ~ *.json { 
            proxy_pass  你的服务器;
         }

我们需要处理逻辑的条件是2个,域名+是否为opation请求
所以我们Nginx需要对这2个条件进行判断,本来是很简单的事情,但

!!!nginx不支持多重判断

所以,配置成了这样


server {
        listen       80; #监听80端口,可以改成其他端口
        server_name  localhost; # 当前服务的域名

        location ~ *.json { 
            proxy_pass  你的服务器;
                       set $flag 0;
                
            if ($http_origin ~ (域名A| 域名B)){
                set $flag "${flag}1";
            }
            if ($request_method = 'OPTIONS'){
                set $flag "${flag}2";
            }
            if ( $flag = "012" ){
                  add_header 'Access-Control-Allow-Origin' '$http_origin';
                                  add_header 'Access-Control-Allow-Credentials' 'true';
                                  add_header 'Access-Control-Allow-Headers' 'X-PINGOTHER,Content-Type,Accept,Origin,User-Agent,Cache-Control,isOutput';
                                  add_header 'Access-Control-Allow-Methods' 'GET,POST,OPTIONS';
                    
                      return 204;
            }


             if ( $flag = "01" ){
                                add_header 'Access-Control-Allow-Origin' '$http_origin';
                                  add_header 'Access-Control-Allow-Credentials' 'true';
                                  add_header 'Access-Control-Allow-Headers' 'X-PINGOTHER,Content-Type,Accept,Origin,User-Agent,Cache-Control,isOutput';
                                  add_header 'Access-Control-Allow-Methods' 'GET,POST,OPTIONS';
                    
                        }


         }

千万别忘了,opation预检请求后,仍然要添加信息,只是不需要直接返回204而已

6.跨域和CDN的坑

当一切在开发环境和测试环境验证无误后,上生产发现了新的问题。
以A域名为例,已经在Nginx上配置了A域名可以跨域

  • 问题的发现:
    生产验证环节,发现A域名下部分网址可以正常响应,部分网址无法正常相应。
    正常跨域请求的Options请求和Get请求都可以获得服务器配置的请求头,有Access-Control各个字段,而不正常的请求,只有Options请求有,Get请求没有,怀疑是Get请求没有达到服务器

  • 6.1 第一步
    观察后发现,不能正常响应的请求,都是.sjson结尾,此结尾代表着和CDN厂商规定的静态接口。因此怀疑是CDN的问题。
    将不好的请求,改变Url的某个时间戳参数,请求正常执行。因此CDN是肯定是原因之一

  • 6.2第二步
    如果问题都在CDN上,则是因为请求到CDN层直接击中缓存的原因,没有达到服务器层获得最新的跨域配置

继续观察:在 .sjson结尾的请求中,也有部分参数请求是好的,部分请求参数是不好的
那么第二个问题来了,请求要么全是好的,要么全是不好的,为何会有概率的发生这种情况

因此怀疑是部分Nginx配置有误,进行排查,发现所有服务器都配置都已正常替换,服务器重新时间都在版本当晚
(这里因为CDN的缓存机制不了解,因此思考出现了问题,就卡住了)

  • 6.3 第三步
    联系CDN服务厂商,了解了CDN的推送策略有2种,公司的默认是第二种
    一是物理上直接将目录下资源删除
    二是将CDN目录下的资源置为过期,CDN会回源,比较新的资源是否变化,如果没变化,则继续使用,有变化,则更新

当时立马让厂商进行了第一种方式的删除,而没有经过分析,第二次错过了发现问题的机会。
厂商删除目录下资源后,再次访问,发现.sjson 的请求,还是部分正常 部分不正常

  • 6.4第四步
    陷入僵局后,再次搜寻资料,病急乱投医,包括怀疑CDN节点不同步等等。
    这里经CDN厂商提醒,跨域请求的网址,正常不跨域的请求也会访问,2份缓存是同一份,可能存在这个问题,这样就可以解释为什么部分请求成功,部分不成功的问题。因为清楚缓存后,哪个请求先到,就会缓存哪份

  • 6.5五.如何验证
    厂商同事,用有问题的请求,分别发送了带Origin字段和不带Origin的字段,用md5计算返回报文,比较发现一致。确认是同一份缓存。

  • 6.6六.如何解决
    一是前端特殊处理,跨域的请求,加特殊的字段参数,但这需要修改代码
    二是CDN厂商提供的,根据http请求头里的Origin字段,为每个值维护一份缓存
    最终选择了第二个方案

  • 6.7继续测试
    为了不影响生产,对第二个方案进行测试
    厂商提供了一个配置了新的规则的测试CDN节点

比较请求:
curl -vo ~/tmp 'https://36.250.240.133/*****.sjson?*****&updTs=20180706004105' -H "Host:访问的域名" -k -H "Origin: 跨域的域名"

curl -vo ~/tmp 'https://36.250.240.133/*****.sjson?*****&updTs=20180706004105' -H "Host:访问的域名" -k -H "Origin: 不跨域的域名"

这样就是2份缓存了,可以比较返回的报文,最终解决了问题。

  • 6.8反思点
    这里有对CDN缓存机制不了解的原因,虽然知道CDN回源策略有定时,也有Url改变,但没有想到,这个请求头里的参数是不比较的。同样,以后CDN的接口如果有cookie等请求头信息,都要注意。不过静态请求也不应该包含那些变化的东西。
    在了解了CDN推送的策略时,其实就应该想到过期后,CDN回源比较没更新,说明了请求的返回报文就是和跨域配置改变前是一样的,这就是同一份缓存
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,937评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,503评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,712评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,668评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,677评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,601评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,975评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,637评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,881评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,621评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,710评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,387评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,971评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,947评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,189评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,805评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,449评论 2 342

推荐阅读更多精彩内容

  • 欢迎关注微信公众号:全栈工厂 本文主要参考跨域资源共享 CORS 详解[http://www.ruanyifeng...
    liqingbiubiu阅读 1,823评论 0 3
  • 题目1.什么是同源策略? 同源策略(Same origin Policy): 浏览器出于安全方面的考虑,只允许与本...
    FLYSASA阅读 1,708评论 0 6
  • 前段时间,被一个问题整了一天。没错,整整一天,后来我感冒了,不知道是不是和这个事情有关。 问题缘由 因为需求,所以...
    一溪酒阅读 262评论 0 0
  • 什么是跨域 跨域,是指浏览器不能执行其他网站的脚本。它是由浏览器的同源策略造成的,是浏览器对JavaScript实...
    Yaoxue9阅读 1,286评论 0 6
  • 什么是跨域 跨域,是指浏览器不能执行其他网站的脚本。它是由浏览器的同源策略造成的,是浏览器对JavaScript实...
    HeroXin阅读 826评论 0 4