關于瀏覽器跨域訪問

生產(chǎn)上上線了展示能力輸出功能,此功能將把內(nèi)部的H5頁面,在其他APP可以嵌入,涉及到跨域訪問的問題
進行總結(jié)
參考文檔:
跨域資源共享 CORS 詳解
跨域的那些事兒

1.什么是跨域

別笑,之前我還真不知道
什么是跨域?
跨域,指的是瀏覽器不能執(zhí)行其他網(wǎng)站的腳本。它是由瀏覽器的同源策略造成的,是瀏覽器施加的安全限制。

所謂同源是指,域名,協(xié)議,端口均相同,不明白沒關系,舉個栗子:
http://www.123.com/index.html 調(diào)用 http://www.123.com/server.php (非跨域)
http://www.123.com/index.html 調(diào)用 http://www.456.com/server.php (主域名不同:123/456,跨域)
http://abc.123.com/index.html 調(diào)用 http://def.123.com/server.php (子域名不同:abc/def,跨域)
http://www.123.com:8080/index.html 調(diào)用 http://www.123.com:8081/server.php (端口不同:8080/8081,跨域)
http://www.123.com/index.html 調(diào)用 https://www.123.com/server.php (協(xié)議不同:http/https,跨域)
請注意:localhost和127.0.0.1雖然都指向本機,但也屬于跨域。
瀏覽器執(zhí)行javascript腳本時,會檢查這個腳本屬于哪個頁面,如果不是同源頁面,就不會被執(zhí)行。

2.瀏覽器的同源策略

同源策略又分為以下兩種

  • DOM同源策略:禁止對不同源頁面DOM進行操作。這里主要場景是iframe跨域的情況,不同域名的iframe是限制互相訪問的。
    XmlHttpRequest同源策略:禁止使用XHR對象向不同源的服務器地址發(fā)起HTTP請求。
  • 只要協(xié)議、域名、端口有任何一個不同,都被當作是不同的域,之間的請求就是跨域操作。

3.為什么我們需要跨域限制

主要是出于安全的考慮
AJAX同源策略主要用來防止CSRF攻擊。如果沒有AJAX同源策略,相當危險,我們發(fā)起的每一次HTTP請求都會帶上請求地址對應的cookie,那么可以做如下攻擊:

  1. 用戶登錄了自己的銀行頁面 http://mybank.comhttp://mybank.com向用戶的cookie中添加用戶標識。
  2. 用戶瀏覽了惡意頁面 http://evil.com。執(zhí)行了頁面中的惡意AJAX請求代碼。
  3. http://evil.comhttp://mybank.com發(fā)起AJAX HTTP請求,請求會默認把http://mybank.com對應cookie也同時發(fā)送過去。
  4. 銀行頁面從發(fā)送的cookie中提取用戶標識,驗證用戶無誤,response中返回請求數(shù)據(jù)。此時數(shù)據(jù)就泄露了。
  5. 而且由于Ajax在后臺執(zhí)行,用戶無法感知這一過程。

DOM同源策略也一樣,如果iframe之間可以跨域訪問,可以這樣攻擊:

  1. 做一個假網(wǎng)站,里面用iframe嵌套一個銀行網(wǎng)站 http://mybank.com
  2. 把iframe寬高啥的調(diào)整到頁面全部,這樣用戶進來除了域名,別的部分和銀行的網(wǎng)站沒有任何差別。
  3. 這時如果用戶輸入賬號密碼,我們的主網(wǎng)站可以跨域訪問到http://mybank.com的dom節(jié)點,就可以拿到用戶的輸入了,那么就完成了一次攻擊。

4.如何做到合理的跨域訪問---CORS

理論基礎:CORS:”跨域資源共享”(Cross-origin resource sharing),這是一個W3C標準
CORS需要瀏覽器和服務器同時支持。目前,所有瀏覽器都支持該功能,IE瀏覽器不能低于IE10。
整個CORS通信過程,都是瀏覽器自動完成,不需要用戶參與。對于開發(fā)者來說,CORS通信與同源的AJAX通信沒有差別,代碼完全一樣。瀏覽器一旦發(fā)現(xiàn)AJAX請求跨源,就會自動添加一些附加的頭信息,有時還會多出一次附加的請求,但用戶不會有感覺。
因此,實現(xiàn)CORS通信的關鍵是服務器。只要服務器實現(xiàn)了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相關的頭信息字段。這時,瀏覽器就會認定,服務器不同意預檢請求,因此觸發(fā)一個錯誤,被XMLHttpRequest對象的onerror回調(diào)函數(shù)捕獲。控制臺會打印出如下的報錯信息。

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

該字段可選,用來指定本次預檢請求的有效期,單位為秒。上面結(jié)果中,有效期是20天(1728000秒),即允許緩存該條回應1728000秒(即20天),在此期間,不用發(fā)出另一條預檢請求。

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 作為服務器開發(fā)者,我們到底怎么做的

5.1 我們需要做什么

可以從上文的流程中看到,服務器的開發(fā)者,需要做的時候分2步

  • 1.在預檢請求中,添加信息,并返回200/204
    204是一個沒有響應體的成功響應

  • 2.在后續(xù)的請求中,繼續(xù)添加Access-Control的信息

5.2 實際操作

生產(chǎn)我們用Nginx作為反向代理,所以我們需要再Nginx進行處理

  • 一個普通的Nginx配置
server {
        listen       80; #監(jiān)聽80端口,可以改成其他端口
        server_name  localhost; # 當前服務的域名

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

我們需要處理邏輯的條件是2個,域名+是否為opation請求
所以我們Nginx需要對這2個條件進行判斷,本來是很簡單的事情,但

!!!nginx不支持多重判斷

所以,配置成了這樣


server {
        listen       80; #監(jiān)聽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的坑

當一切在開發(fā)環(huán)境和測試環(huán)境驗證無誤后,上生產(chǎn)發(fā)現(xiàn)了新的問題。
以A域名為例,已經(jīng)在Nginx上配置了A域名可以跨域

  • 問題的發(fā)現(xiàn):
    生產(chǎn)驗證環(huán)節(jié),發(fā)現(xiàn)A域名下部分網(wǎng)址可以正常響應,部分網(wǎng)址無法正常相應。
    正常跨域請求的Options請求和Get請求都可以獲得服務器配置的請求頭,有Access-Control各個字段,而不正常的請求,只有Options請求有,Get請求沒有,懷疑是Get請求沒有達到服務器

  • 6.1 第一步
    觀察后發(fā)現(xiàn),不能正常響應的請求,都是.sjson結(jié)尾,此結(jié)尾代表著和CDN廠商規(guī)定的靜態(tài)接口。因此懷疑是CDN的問題。
    將不好的請求,改變Url的某個時間戳參數(shù),請求正常執(zhí)行。因此CDN是肯定是原因之一

  • 6.2第二步
    如果問題都在CDN上,則是因為請求到CDN層直接擊中緩存的原因,沒有達到服務器層獲得最新的跨域配置

繼續(xù)觀察:在 .sjson結(jié)尾的請求中,也有部分參數(shù)請求是好的,部分請求參數(shù)是不好的
那么第二個問題來了,請求要么全是好的,要么全是不好的,為何會有概率的發(fā)生這種情況

因此懷疑是部分Nginx配置有誤,進行排查,發(fā)現(xiàn)所有服務器都配置都已正常替換,服務器重新時間都在版本當晚
(這里因為CDN的緩存機制不了解,因此思考出現(xiàn)了問題,就卡住了)

  • 6.3 第三步
    聯(lián)系CDN服務廠商,了解了CDN的推送策略有2種,公司的默認是第二種
    一是物理上直接將目錄下資源刪除
    二是將CDN目錄下的資源置為過期,CDN會回源,比較新的資源是否變化,如果沒變化,則繼續(xù)使用,有變化,則更新

當時立馬讓廠商進行了第一種方式的刪除,而沒有經(jīng)過分析,第二次錯過了發(fā)現(xiàn)問題的機會。
廠商刪除目錄下資源后,再次訪問,發(fā)現(xiàn).sjson 的請求,還是部分正常 部分不正常

  • 6.4第四步
    陷入僵局后,再次搜尋資料,病急亂投醫(yī),包括懷疑CDN節(jié)點不同步等等。
    這里經(jīng)CDN廠商提醒,跨域請求的網(wǎng)址,正常不跨域的請求也會訪問,2份緩存是同一份,可能存在這個問題,這樣就可以解釋為什么部分請求成功,部分不成功的問題。因為清楚緩存后,哪個請求先到,就會緩存哪份

  • 6.5五.如何驗證
    廠商同事,用有問題的請求,分別發(fā)送了帶Origin字段和不帶Origin的字段,用md5計算返回報文,比較發(fā)現(xiàn)一致。確認是同一份緩存。

  • 6.6六.如何解決
    一是前端特殊處理,跨域的請求,加特殊的字段參數(shù),但這需要修改代碼
    二是CDN廠商提供的,根據(jù)http請求頭里的Origin字段,為每個值維護一份緩存
    最終選擇了第二個方案

  • 6.7繼續(xù)測試
    為了不影響生產(chǎn),對第二個方案進行測試
    廠商提供了一個配置了新的規(guī)則的測試CDN節(jié)點

比較請求:
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改變,但沒有想到,這個請求頭里的參數(shù)是不比較的。同樣,以后CDN的接口如果有cookie等請求頭信息,都要注意。不過靜態(tài)請求也不應該包含那些變化的東西。
    在了解了CDN推送的策略時,其實就應該想到過期后,CDN回源比較沒更新,說明了請求的返回報文就是和跨域配置改變前是一樣的,這就是同一份緩存
最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,283評論 6 530
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 97,947評論 3 413
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,094評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,485評論 1 308
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,268評論 6 405
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 54,817評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,906評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,039評論 0 285
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,551評論 1 331
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,502評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,662評論 1 366
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,188評論 5 356
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 43,907評論 3 345
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,304評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,563評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,255評論 3 389
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,637評論 2 370

推薦閱讀更多精彩內(nèi)容

  • 歡迎關注微信公眾號:全棧工廠 本文主要參考跨域資源共享 CORS 詳解[http://www.ruanyifeng...
    liqingbiubiu閱讀 1,855評論 0 3
  • 題目1.什么是同源策略? 同源策略(Same origin Policy): 瀏覽器出于安全方面的考慮,只允許與本...
    FLYSASA閱讀 1,738評論 0 6
  • 前段時間,被一個問題整了一天。沒錯,整整一天,后來我感冒了,不知道是不是和這個事情有關。 問題緣由 因為需求,所以...
    一溪酒閱讀 279評論 0 0
  • 什么是跨域 跨域,是指瀏覽器不能執(zhí)行其他網(wǎng)站的腳本。它是由瀏覽器的同源策略造成的,是瀏覽器對JavaScript實...
    Yaoxue9閱讀 1,309評論 0 6
  • 什么是跨域 跨域,是指瀏覽器不能執(zhí)行其他網(wǎng)站的腳本。它是由瀏覽器的同源策略造成的,是瀏覽器對JavaScript實...
    HeroXin閱讀 841評論 0 4