天天看點

JavaScript——跨域

因為浏覽器出于安全考慮,有同源政策。也就是說,如果協定、域名或者端口有一個不同就是跨域,Ajax 請求會失敗。主要是用來防止 CSRF 攻擊的。簡單點說,CSRF 攻擊是利用使用者的登入态發起惡意請求。也就是說,沒有同源政策的情況下,A 網站可以被任意其他來源的 Ajax 通路到内容。如果你目前 A 網站還存在登入态,那麼對方就可以通過 Ajax 獲得你的任何資訊。當然跨域并不能完全阻止 CSRF。然後我們來考慮一個問題,請求跨域了,那麼請求到底發出去沒有? 請求必然是發出去了,但是浏覽器攔截了響應。你可能會疑問明明通過表單的方式可以發起跨域請求,為什麼 Ajax 就不會。因為歸根結底,跨域是為了阻止使用者讀取到另一個域名下的内容,Ajax 可以擷取響應,浏覽器認為這不安全,是以攔截了響應。但是表單并不會擷取新的内容,是以可以發起跨域請求。同時也說明了跨域并不能完全阻止 CSRF,因為請求畢竟是發出去了。

幾種常見的方式來解決跨域的問題。

JSONP

JSONP 的原理很簡單,就是利用

<script>

标簽沒有跨域限制的漏洞。通過

<script>

标簽指向一個需要通路的位址并提供一個回調函數來接收資料當需要通訊時。
<script src="http://domain/api?param1=a&param2=b&callback=jsonp"></script>
<script>
    function jsonp(data) {
    	console.log(data)
	}
</script>    
           

JSONP 使用簡單且相容性不錯,但是隻限于 get 請求。

在開發中可能會遇到多個 JSONP 請求的回調函數名是相同的,這時候就需要自己封裝一個 JSONP,

function jsonp(url, jsonpCallback, success) {
  let script = document.createElement('script')
  script.src = url
  script.async = true
  script.type = 'text/javascript'
  window[jsonpCallback] = function(data) {
    success && success(data)
  }
  document.body.appendChild(script)
}
jsonp('http://xxx', 'callback', function(value) {
  console.log(value)
})
           

CORS

CORS 需要浏覽器和後端同時支援。IE 8 和 9 需要通過 XDomainRequest 來實作。

浏覽器會自動進行 CORS 通信,實作 CORS 通信的關鍵是後端。隻要後端實作了 CORS,就實作了跨域。

服務端設定 Access-Control-Allow-Origin 就可以開啟 CORS。 該屬性表示哪些域名可以通路資源,如果設定通配符則表示所有網站都可以通路資源。

雖然設定 CORS 和前端沒什麼關系,但是通過這種方式解決跨域問題的話,會在發送請求時出現兩種情況,分别為簡單請求和複雜請求。

簡單請求

以 Ajax 為例,當滿足以下條件時,會觸發簡單請求

使用下列方法之一:

GET

HEAD

POST

Content-Type 的值僅限于下列三者之一:

text/plain

multipart/form-data

application/x-www-form-urlencoded

請求中的任意 XMLHttpRequestUpload 對象均沒有注冊任何事件監聽器; XMLHttpRequestUpload 對象可以使用 XMLHttpRequest.upload 屬性通路。

複雜請求

那麼很顯然,不符合以上條件的請求就肯定是複雜請求了。

對于複雜請求來說,首先會發起一個預檢請求,該請求是 option 方法的,通過該請求來知道服務端是否允許跨域請求。

對于預檢請求來說,如果你使用過 Node 來設定 CORS 的話,可能會遇到過這麼一個坑。

以下以 express 架構舉例:

app.use((req, res, next) => {

res.header(‘Access-Control-Allow-Origin’, ‘*’)

res.header(‘Access-Control-Allow-Methods’, ‘PUT, GET, POST, DELETE, OPTIONS’)

res.header(

‘Access-Control-Allow-Headers’,

‘Origin, X-Requested-With, Content-Type, Accept, Authorization, Access-Control-Allow-Credentials’

)

next()

})

該請求會驗證你的 Authorization 字段,沒有的話就會報錯。

目前端發起了複雜請求後,你會發現就算你代碼是正确的,傳回結果也永遠是報錯的。因為預檢請求也會進入回調中,也會觸發 next 方法,因為預檢請求并不包含 Authorization 字段,是以服務端會報錯。

想解決這個問題很簡單,隻需要在回調中過濾 option 方法即可

res.statusCode = 204

res.setHeader(‘Content-Length’, ‘0’)

res.end()

document.domain

該方式隻能用于二級域名相同的情況下,比如 a.test.com 和 b.test.com 适用于該方式。

隻需要給頁面添加 document.domain = ‘test.com’ 表示二級域名都相同就可以實作跨域

postMessage

這種方式通常用于擷取嵌入頁面中的第三方頁面資料。一個頁面發送消息,另一個頁面判斷來源并接收消息

// 發送消息端
window.parent.postMessage('message', 'http://test.com')
// 接收消息端
var mc = new MessageChannel()
mc.addEventListener('message', event => {
  var origin = event.origin || event.originalEvent.origin
  if (origin === 'http://test.com') {
    console.log('驗證通過')
  }
})
           

繼續閱讀