因為安全性的考量,瀏覽器預設都會限制網頁做跨網域的連線。但如果要提供資料存取的服務給其它人使用,就必須要開放對應的API給其它人連線。而 CORS 就是一個瀏覽器做跨網域連線的時要遵守的規範。
當一個網頁送出一個 request 的時候,瀏覽器會在 request (XMLHttpRequest) 的 header 塞入 Origin 這個資料,這個資料就是代表這個網頁的來源。 Origin 通常就是這個網頁對應網址的網域,也就是網頁發出的 request 必須與原本的網頁要有相同的來源(the same-origin policy)。當一個 request 的 Origin 網域與 request 的目標伺服器不同就是所謂的跨網域連線。
CORS是一個瀏覽器做跨網域連線的方式。透過 HTTP header 的設定,可以規範瀏覽器在進行跨網域連線時可以存取的資料權限與範圍,包括哪些來源可以存取,或是哪些 HTTP verb, header 的 request 可以存取。
當一個支援 CORS 瀏覽器在網頁送出一個 request 時,會做下面的動作:
OPTIONS /resources/post-here/ HTTP/1.1
Host: bar.other
User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.1b3pre) Gecko/20081130 Minefield/3.1b3pre
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Connection: keep-alive
Origin: http://foo.example
Access-Control-Request-Method: POST
Access-Control-Request-Headers: X-PINGOTHER, Content-Type
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://foo.example
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Headers: X-PINGOTHER, Content-Type
Access-Control-Max-Age: 86400
Vary: Accept-Encoding, Origin
Content-Encoding: gzip
Content-Length: 0
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Content-Type: text/plain
如果要做跨網域連線,伺服器端在送出 preflight response 時,就必須要帶有特定的 header。在 rails 中可以在 config 中設定 header 如下:
config/application.rbconfig.action_dispatch.default_headers = {
'Access-Control-Allow-Origin' => '*',
'Access-Control-Request-Method' => 'GET, PATCH, PUT, POST, OPTIONS, DELETE',
'Access-Control-Allow-Headers:' => 'Origin, X-Requested-With, Content-Type, Accept'
}
但非常不建議這樣做,因為這樣的設定會讓所有的 response 都開放跨網域連線,顯然是個不安全的行為。比較好的做法是使用rack-cors這個 gem 來處理 CORS,它的用法也很簡單,只要在 config 中加入相關的設定就可以了:
config/application.rbconfig.middleware.insert_before 0, Rack::Cors do
allow do
origins '*'
resource '/api/*', :headers => :any, :methods => [:get, :post, :options]
end
end
注意要記得調整設定裡的 resource 參數來限制哪些網址要開放跨網域連線,更多細節請參考官方文件。