Cross Domain Access In Rails

1 分鐘閱讀

前言

透過 ajax 發送跨網域存取的 Request, 會因為 broswer 的 Same-origin policy 的安全協定而被擋了下來,如果需要開放部份資料的 API ,來讓外部網域存取,此時就會需要 cross domain 來取得資料,下面就是紀錄一下在 rails 裡要怎麼設定 Cross-Origin。

使用Cross-Origin Resource Sharing (CORS)

js 實作

如果需要把自己的 cookie 帶給 server 的話,要設定 withCredentialstrue 詳細可參考 Requests with credentials

$.ajax({
          url:         "http://localhost:3000/api/user/info",
          accepts:     "application/json",
          contentType: "application/json",
          method:      "GET",
          xhrFields: {
            withCredentials: true
          }
        })

設定 header

如果從 broswer 有發出跨網域的 request 的話,broswer 會先送一個 http method 為 OPTION 的 request,目的是為了要跟 server 確認伺服器是否接受跨網域存取, 如果 server 允許跨網域存取就會在 header 裡寫入相關的訊息,然後 broswer 接收到後就會再一次發出 request 去跟 server 要資料。

Access-Control-Allow-Origin: 允許跨網域存取的 domain white list。

Access-Control-Allow-Methods: 允許使用哪幾種 http method。

Access-Control-Allow-Headers: 允許夾帶哪幾種 header。

Access-Control-Allow-Credentials: 允許 broswer 帶 cookie 上來和伺服器端交換資料。

因為 broswer會送兩次 request 一次為 OPTIONS 另一次為 GET, 所以 routes 要設定。

# controller
module API
  class UsersController < BaseController
    def info
      headers['Access-Control-Allow-Headers'] =
        'Origin, X-Requested-With, Content-Type, Accept'
      headers['Access-Control-Allow-Methods'] = 'POST, GET, OPTIONS'
      headers['Access-Control-Allow-Origin'] =  'http://localhost:3001'
      headers['Access-Control-Allow-Credentials'] = 'true'

      render status: 200, json: current_user
    end
  end
end
# routes
match '/api/user/info', to: 'users#info', via: [:options, :get]

使用 JSONP(JSON with Padding)來實作

JSONP wiki

js 實作

方法ㄧ

$.ajax({
  url:      "http://localhost:3000/api/v1/user/info",
  dataType: "jsonp"
})
.done((data) => {
  console.log(data);
});

方法二

這邊需要注意的是如果是用 $.getJSON 的話,在 URL 後面一定要加 ?callback=?,不然 broswer 還是會跟你說沒有設定Access-Control-Allow-Origin

$.getJSON("http://localhost:3000/api/v1/user/info?callback=?", function(data){
  console.log(data)
});

rails server 端實作

這邊要注意的點是 return 的格式要是 json,然後要記得多 return 一個 callback 的值回去,不然會吐下面的error

Refused to execute script from ‘http://localhost:3000/api/user/info?callback=jQuery321035603866783693631497340429353&=1497340429354’ because its MIME type (‘application/json’) is not executable, and strict MIME type checking is enabled.

另外因為 jsonp 只能用 GET method, 所以 routes 也設定 get 就可以了。

module API
  class UsersController < BaseController
    def info
      render status: 200, json: current_user, callback: params[:callback]
    end
  end
end
# 透過jsonp送到server的params會是長這個樣子
{"callback"=>"jQuery32102229570348418879_1497337933526",
 "_"=>"1497337933527",
 "format"=>:json,
 "controller"=>"api/users",
 "action"=>"info"}

使用 rack-cors gem 實作

跟前面兩種 CORSJOSNP 的方式相比,用Rack CORS Middleware方式是最簡單的(因為大大都幫忙寫好了,主要是透過 Middleware 的方式去做設定。

Indtall

gem 'rack-cors', :require => 'rack/cors'

setting

需要注意的是 rails3,4 跟 rails 5 的設定方法是不同的,如果有需要 withCredentials 的話可以再加上 credentials: true 的參數。

origins: 跟上面一樣是指定允許跨網域存取的 Domain。

resource: 可以指定特定的URL。

還有其他的設定可以到 ack-cors 的 readme 看。

# config/application.rb
module YourApp
  class Application < Rails::Application

    # ...

    # Rails 3/4

    config.middleware.insert_before 0, "Rack::Cors" do
      allow do
        origins '*'
        resource '*', :headers => :any, :methods => [:get, :post, :options]
      end
    end

    # Rails 5

    config.middleware.insert_before 0, Rack::Cors do
      allow do
        origins 'localhost:3000'
        resource '*', :headers => :any, :methods => [:get, :post, :options]
      end
    end

  end
end

Referance

HTTP access control (CORS)

Changing getJSON to JSONP

Supporting Cross-Domain AJAX in Rails using JSONP and CORS

jQuery’s JSONP Explained with Examples

留言