CORS(跨來源資源共用)
https://blog.techbridge.cc/2017/02/25/csrf-introduction
前言
CORS (Cross-Origin Resource Sharing) 是針對不同源的請求而定的規範,透過 JavaScript 存取非同源資源時,server 必須明確告知瀏覽器允許何種請求,只有 server 允許的請求能夠被瀏覽器實際發送,否則會失敗。 在 CORS 的規範裡面,跨來源請求有分兩種:「簡單」的請求和非「簡單」的請求。
簡單跨來源請求
所謂的簡單請求,必須符合下面兩個條件,不符合任一條件的請求就是非簡單請求。
- 只能是 HTTP GET, POST or HEAD 方法
- 自訂的 request header 只能是 Accept、Accept-Language、Content-Language 或 Content-Type(值只能是 application/x-www-form-urlencoded、multipart/form-data 或 text/plain)
這個請求不是一個簡單的請求:
const response = await fetch('https://othersite.com/data', {
method: 'DELETE',
headers: {
'Content-Type': 'application/json',
'CUSTOM-HEADER': 'jin100'
}
});
違反簡單請求的地方有三個,分別是:(1) http DELETE 方法;(2) Content-Type application/json;(3) 不合規範的 CUSTOM-HEADER。
Origin (來源)
首先,瀏覽器發送跨來源請求時,會帶一個 Origin header,表示這個請求的來源。Origin 包含通訊協定、網域和通訊埠三個部分。
Access-Control-Allow-Origin
當 server 端收到這個跨來源請求時,它可以依據「請求的來源」,亦即 Origin 的值,決定是否要允許這個跨來源請求。授權的方法是在 response 的 header 加上 Access-Control-Allow-Origin,值則為請求的來源。如果 server 允許任何來源的跨來源請求,那可以直接回 *。
非簡單跨來源請求
瀏覽器在發送請求之前會先發送一個 「preflight request(預檢請求)」,其作用在於先問伺服器:你是否允許這樣的請求?真的允許的話,我才會把請求完整地送過去。 預檢請求(Preflight request)是一個 http OPTIONS 方法,會帶有兩個 request header:Access-Control-Request-Method 和 Access-Control-Request-Headers。
例子:非簡單請求
const response = await fetch('https://othersite.com/data', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'CUSTOM-HEADER': 'jin100'
}
});
瀏覽器幫我們發送的預檢請求(preflight request)就會像這樣:
OPTIONS /data
Host: othersite.com
Origin: https://shubo.io
Access-Control-Request-Method: POST
Access-Control-Request-Headers: CUSTOM-HEADER, Content-Type
預檢回應(Preflight Response)會帶有兩個 request header:Access-Control-Request-Method 和 Access-Control-Request-Headers。當瀏覽器看到跨來源請求的方法和 header 都有被列在允許的方法和 header 中,就表示可以實際發送請求了。
Access-Control-Allow-Methods: POST
Access-Control-Allow-Headers: CUSTOM-HEADER, Content-Type
跨來源請求的 Cookie
一般的 http request 會帶有該網域底下的 cookie;然而,跨來源請求預設是不能帶 cookie 的。請求必須要明確地標示「我要存取跨域 cookie」。使用 fetch API 和 XMLHttpRequest 的設定方法如下:
fetch('https://othersite.com/data', {
credentials: 'include'
})
const xhr = new XMLHttpRequest();
xhr.withCredentials = true;
xhr.open('POST', 'https://othersite.com/data');
Server 端也需要額外的設定:如果是信任的來源,回應要帶有 Access-Control-Allow-Credentials header: 如果是允許使用 cookie 的情況,Access-Control-Allow-Origin 不能用 *,必須明確標示哪些來源允許存取。
Access-Control-Allow-Credentials: true
總結
- 先認清楚是否為「簡單」的跨來源請求,如果是,在後端 GET/POST/HEAD 方法本身加上 Access-Control-Allow-Origin header。
- 如果非「簡單」跨來源請求,在後端 OPTIONS 加上 Access-Control-Allow-Methods 及 Access-Control-Allow-Headers header。另外,在後端方法本身加上 Access-Control-Allow-Origin header。
- (可選) 需要使用 cookie 的情況下,前端要加上 credentials: 'include' 或是 withCredentials 參數,後端要加上 Access-Control-Allow-Credentials header,而且 Access-Control-Allow-Origin header 不能用 *。