CORS跨域限制以及預請求驗證

littlebirdflying發表於2018-09-22

CORS 的使用

建立兩個伺服器,進入對應目錄,命令列 node server.js,node server2.js,啟動伺服器。

server.js 會讀取 test.html,在8888埠顯示,test.html傳送跨域請求8887伺服器,server2.js通過設定'Access-Control-Allow-Origin': 'http://127.0.0.1:8888'允許跨域。

瀏覽器認為localhost和127.0.0.1不同,所以本地用127.0.0.1

// server.js
const http = require('http')
const fs = require('fs')

http.createServer(function (request, response) {
  console.log('request come', request.url)

  const html = fs.readFileSync('test.html', 'utf8') // 注意設定 utf8,這樣讀取的是字串,否則是二進位制的資料
  response.writeHead(200, {
    'Content-Type': 'text/html' // 不設定的話,瀏覽器會直接顯示,不解析;預設瀏覽器會加上
  })
  response.end(html)
}).listen(8888)

console.log('server listening on 8888')
複製程式碼
// server2.js
const http = require('http')

http.createServer(function (request, response) {
  console.log('request come', request.url)

  response.writeHead(200, { // 不設定這個頭,瀏覽器還是會傳送請求,並接收內容,瀏覽器檢查head頭,如果沒有'Access-Control-Allow-Origin',並且設定為允許,瀏覽器會把本次請求返回的內容會略掉,並且報錯。
    'Access-Control-Allow-Origin': 'http://127.0.0.1:8888', // 允許 8888 的域名訪問,*也可以(不夠安全)
  })
  response.end('123')
}).listen(8887)

console.log('server listening on 8887')
複製程式碼

新增多個頭

通過判斷 request.url 來判斷是否新增不同的 head 頭。

JSONP

瀏覽器允許 link img script中的src或href進行跨域請求,JSONP的原理就是在script標籤中,載入連結,連結訪問了伺服器某個請求,並返回了內容,伺服器返回的內容是可控的,可以在伺服器返回內容中的script標籤中寫一段可執行的JS程式碼,然後呼叫JSONP發起請求的一些內容。

// test.html
<script src="http://127.0.0.1:8887/"></script>
複製程式碼

這樣不設定8887的head頭,也可以進行跨域,得到請求內容。

CORS跨域限制

主要包括 methods content-type 和 請求頭的限制。

允許方法

預設允許方法 GET HEAD POST,其他的方法 PUT DELETE 預設是不允許的。

允許Content-Type

text/plain

multipart/form-data

application/x-www-form-urlencoded

除了這三種,其他的 Content-Type 也需要使用預請求驗證後,才能傳送。

其他限制

  • 請求頭限制,自定義的請求頭預設是不允許的,也需要驗證;官方文件,關於請求頭的詳細資訊https://fetch.spec.whatwg.org/#cors-safelisted-request-header
  • XMLHttpRequestUpload 物件均沒有註冊任何事件監聽器(很少用)
  • 請求中沒有使用 ReadableStream 物件(很少用)

CORS預請求

demo

8888下的 test.html 傳送的請求攜帶了自定義請求頭,從瀏覽器看請求傳送成功,返回狀態碼200,但是瀏覽器會報錯,不允許跨域,資料雖然返回,但被瀏覽器忽略了。

// server.js
const http = require('http')
const fs = require('fs')

http.createServer(function (request, response) {
  console.log('request come', request.url)

  const html = fs.readFileSync('test.html', 'utf8') 
  response.writeHead(200, {
    'Content-Type': 'text/html' 
  })
  response.end(html)
}).listen(8888)

console.log('server listening on 8888')
複製程式碼
// server2.js
const http = require('http')

http.createServer(function (request, response) {
  console.log('request come', request.url)

  response.writeHead(200, { 
    'Access-Control-Allow-Origin': '*'
  })
  response.end('123')
}).listen(8887)

console.log('server listening on 8887')
複製程式碼
<script>
  fetch('http://localhost:8887', { // 傳送請求
    method: 'POST',
    headers: { // 一個自定義的頭
      'X-Test-Cors': '123'
    }
  })
</script>
複製程式碼

實現預請求

const http = require('http')

http.createServer(function (request, response) {
  console.log('request come', request.url)

  response.writeHead(200, { 
    'Access-Control-Allow-Origin': '*',
    'Access-Control-Allow-Headers': 'X-Test-Cors' // 服務端新增允許自定義請求頭
  })
  response.end('123')
}).listen(8887)

console.log('server listening on 8887')
複製程式碼

檢視network,會發現多了一個請求檔案localhost,它的 Request Method 請求方法是 OPTIONS,其他的很正常請求一樣,通過 OPTIONS 請求獲得服務端允許傳送請求的認可。之後再實際傳送 POST 請求。

允許其他請求方法

const http = require('http')

http.createServer(function (request, response) {
  console.log('request come', request.url)

  response.writeHead(200, {
    'Access-Control-Allow-Origin': '*', 
    'Access-Control-Allow-Headers': 'X-Test-Cors',
    'Access-Control-Allow-Methods': 'POST, PUT, DELETE' // 允許使用被限制的請求方法
  })
  response.end('123')
}).listen(8887)

console.log('server listening on 8887')
複製程式碼

跨域請求時間

比如第一次請求,network觀察檔案,會有 Method 是 OPTIONS 的預請求檔案,再次重新整理網頁傳送請求,這個檔案就不會再傳送了,不會出現在 network 列表中。

const http = require('http')

http.createServer(function (request, response) {
  console.log('request come', request.url)

  response.writeHead(200, {
    'Access-Control-Allow-Origin': '*', 
    'Access-Control-Allow-Headers': 'X-Test-Cors',
    'Access-Control-Allow-Methods': 'POST, PUT, DELETE',
    'Access-Control-Max-Age': '1000' // 1000s之內,不需要再傳送預請求進行驗證了,時間內直接發正式請求
  })
  response.end('123')
}).listen(8887)

console.log('server listening on 8887')
複製程式碼

瀏覽器希望通過限制保證服務端的安全

相關文章