成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

通過(guò)實(shí)例理解Web應(yīng)用跨域問(wèn)題

開發(fā) 前端
本文介紹了日常Web應(yīng)用開發(fā)過(guò)程中經(jīng)常遇到的跨域問(wèn)題,探討了“域(Origin)”概念以及跨域問(wèn)題的真實(shí)原因:即瀏覽器的同源策略限制了不同源請(qǐng)求資源的訪問(wèn)。

在開發(fā)Web應(yīng)用的過(guò)程中,我們經(jīng)常會(huì)遇到所謂“跨域問(wèn)題(Cross Origin Problem)”。跨域問(wèn)題是由于瀏覽器的同源策略(Same-origin policy)[1]導(dǎo)致的,它限制了不同源(Origin:域名、協(xié)議或端口)之間的資源交互。在這篇文章中,我將通過(guò)一些具體的示例來(lái)把跨域問(wèn)題以及主流解決方法說(shuō)清楚,供大家參考。

1. 什么是跨域問(wèn)題

跨域問(wèn)題指的是當(dāng)一個(gè)Web應(yīng)用程序在訪問(wèn)另一個(gè)域(Origin)的資源時(shí),瀏覽器會(huì)阻止這個(gè)跨域的請(qǐng)求(Cross Origin Request)。這句針對(duì)跨域問(wèn)題的詮釋里有一個(gè)術(shù)語(yǔ)“域(Origin)”,它到底是什么呢?

1.1 什么是Origin

在Mozilla官方術(shù)語(yǔ)表中,"Origin"指的是一個(gè)Web應(yīng)用/網(wǎng)站的標(biāo)識(shí),由協(xié)議(protocol/scheme)、域名(domain,或主機(jī)名host)和端口(port)組成。如果兩個(gè)應(yīng)用/網(wǎng)站的協(xié)議、域名和端口都相同,它們就被認(rèn)為是同源的(same origin);否則,它們被視為不同源。我們看到:**Origin是一個(gè)典型的三元組(protocol, domain, port)**,只有三元組相同的兩個(gè)應(yīng)用/站點(diǎn)才會(huì)被認(rèn)為是同源的(same origin)。

下面是一些判斷兩個(gè)應(yīng)用/站點(diǎn)是否同源的例子及判斷理由:

圖片圖片

知道了Origin三元組后,我們來(lái)揪出跨域問(wèn)題背后的“罪魁禍?zhǔn)住薄?/p>

1.2 同源策略 - 跨域問(wèn)題的“罪魁禍?zhǔn)住?/span>

瀏覽器為了增加安全性而采取的一項(xiàng)重要措施,那就是“同源策略[2]”。同源策略限制了一個(gè)網(wǎng)頁(yè)中的腳本只能與同源(三元組:協(xié)議、域名、端口相同)的資源進(jìn)行交互,而不能直接訪問(wèn)不同源的資源。

瀏覽器的這種同源策略限制主要包含以下幾點(diǎn):

  • Cookie、LocalStorage和IndexDB無(wú)法讀取非同源的資源。
  • DOM和JS對(duì)象無(wú)法獲得非同源資源。例如iframe、img等標(biāo)簽加載的資源,DOM無(wú)法訪問(wèn);JS無(wú)法操作非同源頁(yè)面的DOM。
  • AJAX請(qǐng)求不能發(fā)送到非同源的域名,瀏覽器會(huì)阻止非同源的AJAX請(qǐng)求。
  • 不能讀取非同源網(wǎng)頁(yè)的Cookie、LocalStorage和IndexDB。

下圖(圖片來(lái)自網(wǎng)絡(luò))展示了同源策略對(duì)惡意腳本代碼對(duì)非同源數(shù)據(jù)訪問(wèn)的限制:

圖片圖片

上面這張圖片清晰地展示了惡意腳本代碼試圖訪問(wèn)非同源數(shù)據(jù)進(jìn)行惡意登錄的過(guò)程。

首先,用戶通過(guò)瀏覽器訪問(wèn)正常網(wǎng)站domain1.com,并用用戶名密碼正常登錄該網(wǎng)站,domain1.com使用cookie技術(shù)[3]在用戶瀏覽器中保存了與用戶登錄domain1.com相關(guān)的會(huì)話信息或token信息。

之后,用戶又訪問(wèn)了惡意站點(diǎn)domain2.com,該站點(diǎn)首頁(yè)的腳本代碼在被下載到用戶瀏覽器中后,試圖訪問(wèn)瀏覽器cookie中有關(guān)domain1.com的cookie信息,并試圖用該信息冒充用戶登錄domain1.com做惡意操作。

瀏覽器的同源策略成功禁止了惡意代碼的這些惡意操作,瀏覽器從domain2.com下載的腳本代碼只能訪問(wèn)與domain2.com同源的信息。

通過(guò)這個(gè)過(guò)程我們看到:瀏覽器同源策略的本意是防止惡意網(wǎng)站通過(guò)腳本竊取用戶的敏感信息,比如登錄憑證、個(gè)人資料等。如果同源策略不存在,惡意網(wǎng)站就可以自由地讀取、修改甚至篡改其他網(wǎng)站的數(shù)據(jù),給用戶和網(wǎng)站帶來(lái)巨大的安全風(fēng)險(xiǎn)。

不過(guò),這種策略的存在給開發(fā)人員在開發(fā)過(guò)程帶來(lái)諸多煩惱,比如:跨域數(shù)據(jù)訪問(wèn)限制、跨域腳本調(diào)用限制以及無(wú)法在不同域名之間共享會(huì)話信息等。為此,開發(fā)人員需要使用一些技術(shù)手段來(lái)解決這些跨域問(wèn)題,這增加了開發(fā)的復(fù)雜性,并且需要額外的配置和處理,給開發(fā)人員帶來(lái)了一定的麻煩。此外,不正確地處理跨域請(qǐng)求也可能導(dǎo)致安全漏洞,因此開發(fā)人員還需要對(duì)跨域請(qǐng)求進(jìn)行合理的安全控制和驗(yàn)證。

1.3 獲取請(qǐng)求中的“origin”

為了做同源檢測(cè),我們需要獲取和確定請(qǐng)求中的origin信息。那么如何讀取和確定呢?

在HTTP請(qǐng)求頭中,"Origin"字段表示發(fā)送請(qǐng)求的頁(yè)面或資源的源信息。該字段包含了發(fā)送請(qǐng)求的頁(yè)面的完整URL或者僅包含協(xié)議、域名和端口的部分URL。

在同源策略下,所有的跨域請(qǐng)求都必須攜帶"Origin"請(qǐng)求頭字段,指示請(qǐng)求的來(lái)源。因此,在符合同源策略的情況下,每個(gè)請(qǐng)求都應(yīng)該攜帶"Origin"字段。

在服務(wù)器端,我們可以通過(guò)讀取請(qǐng)求頭中的"Origin"字段來(lái)確定請(qǐng)求的origin,具體的方法會(huì)根據(jù)使用的編程語(yǔ)言和框架而有所不同,例如在Go中可以通過(guò)r.Header.Get("Origin")來(lái)獲取"Origin"字段的值。由于"Origin"字段是由客戶端提供的,服務(wù)器端在處理請(qǐng)求時(shí),需要進(jìn)行驗(yàn)證和安全性檢查,以防止偽造或惡意的請(qǐng)求。

然而,有些情況下,請(qǐng)求可能不會(huì)攜帶"Origin"字段。例如,非瀏覽器環(huán)境下的請(qǐng)求(如服務(wù)器間的請(qǐng)求、命令行工具等)可能不會(huì)包含"Origin"字段。此外,某些舊版本的瀏覽器可能也不會(huì)發(fā)送"Origin"字段。

在這種情況下,我們就需要通過(guò)其他方式來(lái)確定請(qǐng)求的來(lái)源。例如,服務(wù)端可以查看請(qǐng)求頭中的Referer字段來(lái)獲取請(qǐng)求的來(lái)源。Referer字段指示了請(qǐng)求的來(lái)源頁(yè)面的URL。通過(guò)檢查Referer字段,服務(wù)端可以判斷請(qǐng)求是否來(lái)自不同的域。此外,服務(wù)器端還可以檢查請(qǐng)求頭中的Host字段,該字段指示了請(qǐng)求的目標(biāo)主機(jī)。如果請(qǐng)求的目標(biāo)主機(jī)與服務(wù)端所在的主機(jī)不一致,那么可以判斷請(qǐng)求是跨域的。

不過(guò),需要注意的是,服務(wù)端的這些方法都依賴于請(qǐng)求頭中的信息,而請(qǐng)求頭可以被客戶端偽造或修改。因此,為了更可靠地判斷請(qǐng)求是否跨域,服務(wù)端應(yīng)該綜合考慮多個(gè)因素,并進(jìn)行適當(dāng)?shù)尿?yàn)證和安全措施。

下面我們看一個(gè)可以復(fù)現(xiàn)跨域問(wèn)題的示例。

1.4 復(fù)現(xiàn)跨域問(wèn)題的Go代碼示例

出現(xiàn)跨域問(wèn)題的示例的圖示如下:

圖片圖片

在這個(gè)示例中,我們有兩個(gè)Web應(yīng)用:server1.com:8081和server2.com:8082。根據(jù)前面對(duì)Origin的理解,這兩個(gè)Web應(yīng)用顯然不是同源的。

server1.com和server2.com對(duì)應(yīng)的Go代碼分別如下:

// cross-origin-examples/reproduce/server1.com

func main() {
 http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
  w.Header().Set("Content-Type", "text/html; charset=utf-8")

  html := `
   <!DOCTYPE html>
   <html>
   <head>
    <title>Cross-Origin Example</title>
    <script>
     function makeCrossOriginRequest() {
      var xhr = new XMLHttpRequest();
      xhr.open("GET", "http://server2.com:8082/api/data", true);
      xhr.onreadystatechange = function() {
       if (xhr.readyState === 4 && xhr.status === 200) {
        console.log(xhr.responseText);
       }
      };
      xhr.send();
     }
    </script>
   </head>
   <body>
    <h1>Cross-Origin Example</h1>
    <button notallow="makeCrossOriginRequest()">Make Cross-Origin Request</button>
   </body>
   </html>
  `

  fmt.Fprintf(w, html)
 })

 err := http.ListenAndServe("server1.com:8081", nil)
 if err != nil {
  panic(err)
 }
}


// cross-origin-examples/reproduce/server2.com

package main

import (
 "fmt"
 "net/http"
)

func main() {
 http.HandleFunc("/api/data", func(w http.ResponseWriter, r *http.Request) {
  fmt.Printf("recv request: %#v\n", *r)
  w.Write([]byte("Welcome to api/data"))
 })

 http.ListenAndServe("server2.com:8082", nil)
}

注:在編譯啟動(dòng)上面兩個(gè)程序之前,需要在/etc/hosts中將server1.com和server2.com的地址指為127.0.0.1。

從示意圖來(lái)看,用戶使用瀏覽器與兩個(gè)Web應(yīng)用的交互過(guò)程是這樣的:

首先,用戶通過(guò)瀏覽器訪問(wèn)了server1.com:8081的主頁(yè),并收到server1.com:8081返回的應(yīng)答包體。該應(yīng)答包體是一個(gè)html頁(yè)面,如下圖:

圖片圖片

接下來(lái),用戶點(diǎn)擊“Make Cross-Origin Request”按鈕,頁(yè)面內(nèi)通過(guò)ajax向server2.com:8082/api/data發(fā)起GET請(qǐng)求。

最后,我們?cè)?Edge/Chrome)瀏覽器的控制臺(tái)上將看到下面錯(cuò)誤:

圖片圖片

通過(guò)下面server2.com的日志,我們看到ajax請(qǐng)求已經(jīng)發(fā)到server2.com并被正確處理:

recv request: http.Request{Method:"GET", URL:(*url.URL)(0xc00010a480), Proto:"HTTP/1.1", ProtoMajor:1, ProtoMinor:1, Header:http.Header{"Accept":[]string{"*/*"}, "Accept-Encoding":[]string{"gzip, deflate"}, "Accept-Language":[]string{"zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6"}, "Connection":[]string{"keep-alive"}, "Origin":[]string{"http://server1.com:8081"}, "Referer":[]string{"http://server1.com:8081/"}, "User-Agent":[]string{"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 Edg/116.0.1938.81"}}, Body:http.noBody{}, GetBody:(func() (io.ReadCloser, error))(nil), ContentLength:0, TransferEncoding:[]string(nil), Close:false, Host:"server2.com:8082", Form:url.Values(nil), PostForm:url.Values(nil), MultipartForm:(*multipart.Form)(nil), Trailer:http.Header(nil), RemoteAddr:"127.0.0.1:49773", RequestURI:"/api/data", TLS:(*tls.ConnectionState)(nil), Cancel:(<-chan struct {})(nil), Response:(*http.Response)(nil), ctx:(*context.cancelCtx)(0xc000106320)}

server2.com在服務(wù)端并沒有主動(dòng)判斷是否是同源請(qǐng)求,但即使服務(wù)器沒有進(jìn)行跨域校驗(yàn)并返回成功的響應(yīng)和數(shù)據(jù),瀏覽器也會(huì)攔截腳本讀取跨域響應(yīng)數(shù)據(jù)的嘗試,這是由瀏覽器的同源策略所決定的。這也是我們看到上面截圖中報(bào)錯(cuò)的原因。

那么解決跨域問(wèn)題有哪些主流的解決方法呢?我們繼續(xù)看一下。

2. 跨域問(wèn)題的主流解決方法

為了解決跨域問(wèn)題,有下面幾種常見的解決方法:

  • JSONP(JSON with Padding)

通過(guò)動(dòng)態(tài)創(chuàng)建<script>標(biāo)簽來(lái)加載跨域的JavaScript腳本,進(jìn)而實(shí)現(xiàn)跨域數(shù)據(jù)獲取。

  • CORS[4](跨域資源共享, CORS是Cross-Origin Resource Sharing)

通過(guò)在服務(wù)器響應(yīng)頭中設(shè)置CORS訪問(wèn)策略以允許指定的Origin訪問(wèn)資源。

  • 代理服務(wù)器

在同域下創(chuàng)建一個(gè)代理服務(wù)器,將跨域請(qǐng)求轉(zhuǎn)發(fā)到目標(biāo)服務(wù)器并返回結(jié)果。代理服務(wù)器對(duì)響應(yīng)頭統(tǒng)一增加Access-Control-Allow-Origin等CORS相關(guān)字段,表示允許跨域訪問(wèn)。

其中CORS是解決跨域問(wèn)題時(shí)應(yīng)用最為廣泛的方法。CORS(跨域資源共享)主要是通過(guò)設(shè)置HTTP頭來(lái)解決跨域問(wèn)題的。

服務(wù)器端通過(guò)在響應(yīng)(Response)的HTTP頭中設(shè)置Access-Control-Allow-Origin頭來(lái)設(shè)置允許的請(qǐng)求來(lái)源域(Origin: 三元組)。

如果設(shè)置為“*”,則表示允許任意域發(fā)起跨域請(qǐng)求:

Access-Control-Allow-Origin: *

也可以在響應(yīng)中將Access-Control-Allow-Origin設(shè)置為只允許指定的Origin訪問(wèn)資源,比如:

Access-Control-Allow-Origin: http://server1.com:8081

Access-Control-Allow-Origin頭的值還支持設(shè)置多個(gè)origin,多個(gè)origin用逗號(hào)分隔:

Access-Control-Allow-Origin: http://server1.com:8081,https://server2.com:8082

注:關(guān)于Access-Control-Allow-Origin的值是否要帶上protocol和port的問(wèn)題,我實(shí)測(cè)的情況是必須帶。前面說(shuō)過(guò):Origin是三元組,只有完全相同才算是同源。

此外,域名必須具體到二級(jí)域名才能匹配成功。頂級(jí)域名如“.com”、“.org”是不允許的。

服務(wù)端響應(yīng)的跨域設(shè)置還不僅Access-Control-Allow-Origin一個(gè),我們還可以設(shè)置Access-Control-Allow-Methods、Access-Control-Allow-Headers、Access-Control-Max-Age等字段來(lái)更細(xì)粒度的進(jìn)行跨域訪問(wèn)控制。

注:有些值A(chǔ)ccess-Control-XXX-xxx字段僅用于Preflight Request(預(yù)檢請(qǐng)求)[5],比如:Access-Control-Allow-Methods。CORS Preflight Request是一種CORS請(qǐng)求,它使用特定的方法和Header檢查CORS協(xié)議是否被理解和服務(wù)器是否被感知。它是一個(gè)OPTIONS請(qǐng)求,使用兩個(gè)或三個(gè)HTTP請(qǐng)求頭: Access-Control-Request-Method(訪問(wèn)控制請(qǐng)求方法)、Origin(起源)和可選的 Access-Control-Request-Headers(訪問(wèn)控制請(qǐng)求頭)。

3. 使用CORS解決跨域問(wèn)題的示例

下面我們修改一下server2.com的代碼來(lái)解決前面遇到的跨域問(wèn)題:

// cross-origin-examples/solve/server2.com/main.go

func main() {
    http.HandleFunc("/api/data", func(w http.ResponseWriter, r *http.Request) {
        fmt.Printf("recv request: %#v\n", *r)
        w.Header().Set("Access-Control-Allow-Origin", "http://server1.com:8081")
        w.Write([]byte("Welcome to api/data"))
    })

    http.ListenAndServe("server2.com:8082", nil)
}

我們僅在server2.com/main.go中增加了一行代碼,旨在允許來(lái)自http://server1.com:8081的跨域請(qǐng)求訪問(wèn)server2.com的資源:

w.Header().Set("Access-Control-Allow-Origin", "http://server1.com:8081")

啟動(dòng)新版server2.com后,再點(diǎn)擊頁(yè)面上的“Make Cross-Origin Request”按鈕,我們?cè)跒g覽器的控制臺(tái)上就能看到應(yīng)答成功被接受并顯示。

4. 小結(jié)

本文介紹了日常Web應(yīng)用開發(fā)過(guò)程中經(jīng)常遇到的跨域問(wèn)題,探討了“域(Origin)”概念以及跨域問(wèn)題的真實(shí)原因:即瀏覽器的同源策略限制了不同源請(qǐng)求資源的訪問(wèn)。

接下來(lái)通過(guò)Go代碼示例演示了跨域問(wèn)題的表現(xiàn)形式,并介紹了幾種主要的跨域解決方案,最后對(duì)最常見的CORS解決方案做了細(xì)致說(shuō)明,并用實(shí)例展示了服務(wù)端設(shè)置CORS頭后跨域問(wèn)題的解決。

希望本文可以幫助大家更深入的理解和掌握Web應(yīng)用跨域問(wèn)題以及解決方法。

本文涉及的源碼可以在這里[6]下載。

5. 參考資料

  • The ultimate guide to enabling Cross-Origin Resource Sharing (CORS)[7] - https://blog.logrocket.com/the-ultimate-guide-to-enabling-cross-origin-resource-sharing-cors/
  • Cross-Origin Resource Sharing (CORS)[8] - https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS
  • Glossary: Origin[9] - https://developer.mozilla.org/en-US/docs/Glossary/Origin
  • Same-origin policy[10] - https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy
責(zé)任編輯:武曉燕 來(lái)源: TonyBai
相關(guān)推薦

2023-10-31 07:37:02

2016-11-04 20:02:37

Apache

2023-10-26 08:19:34

2014-08-19 10:36:02

AngularCORS

2017-08-20 12:49:59

瀏覽器跨域服務(wù)器

2018-12-12 15:50:13

2021-06-10 18:11:02

Cors跨域Web開發(fā)Cors

2023-12-25 08:04:42

2018-11-26 14:52:12

Web前端跨域

2022-10-13 14:11:29

瀏覽器域名端口

2021-06-06 13:05:15

前端跨域CORS

2010-07-30 12:48:13

Flex跨域調(diào)用

2024-10-29 16:41:24

SpringBoot跨域Java

2010-01-04 13:15:33

跨域引用Silverl

2010-02-22 17:29:47

WCF跨域

2009-02-18 09:30:10

AJAX跨域XML

2024-12-02 14:30:20

2024-02-27 08:14:51

Nginx跨域服務(wù)

2019-11-11 17:34:16

前端開發(fā)技術(shù)

2015-04-24 10:37:40

Web安全瀏覽器跨域訪問(wèn)
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)

主站蜘蛛池模板: av激情影院 | 亚洲一区二区三区免费在线观看 | 一级在线观看 | 波多野结衣av中文字幕 | 亚洲欧美中文日韩在线v日本 | 中文字幕亚洲欧美日韩在线不卡 | 中文字幕在线一区二区三区 | 亚洲精品视频一区二区三区 | 久久99精品久久久久久 | 波多野结衣亚洲 | 国产精品久久久久久久久久久久 | 久久国产精彩视频 | 男女羞羞的网站 | 日本精品视频在线观看 | 玖玖国产 | 亚洲欧美在线一区 | 久久国产精品视频 | 91免费小视频 | 欧美日韩一区二区三区四区 | 综合精品 | 黄色中文字幕 | 金莲网 | 国产重口老太伦 | 国产精品污www一区二区三区 | 亚洲人成人一区二区在线观看 | 嫩草最新网址 | 久久亚洲一区二区三 | 国产一区二 | 免费视频一区 | 欧洲妇女成人淫片aaa视频 | 日韩一区二区三区视频在线观看 | 欧美xxxx色视频在线观看免费 | 亚洲国产一区二区三区四区 | 国产精品久久一区二区三区 | 男人天堂社区 | 亚洲 欧美 另类 综合 偷拍 | av一区二区三区四区 | 午夜视频精品 | 亚洲国产视频一区 | 欧美日韩在线成人 | 亚洲国产精品久久久久 |