系統服務化構建-跨域CROS

  • 2019 年 12 月 10 日
  • 筆記

文本討論關於接口開發中的跨域 CORS。CORS 是一種瀏覽器協議,源於 HTTP 請求的安全策略,在這個體系中的關鍵詞有,同源策略,XMLHttpRequest,Ajax,和前後端分離。

尤其是在目前業界前後端分離的大趨勢下,跨域是一種常見的前後端開發通訊(communicate)方式。

「The basic idea behind CORS is to use custom HTTP headers to allow both the browser and the server to know enough about each other to determine if the request or response should succeed or fail.

「以上一段話參考 Cross-domain Ajax with Cross-Origin Resource Sharing/[1],主要含義是說 CORS 的核心思想是通過 HTTP 請求頭通訊,使得客戶端和服務器端彼此決定請求和響應是否被成功接受。

Using CORS[2]這裡有一篇關於跨域技術的完整闡述,感興趣的可以閱讀。

「CORS 協議的實現需要客戶端和服務器端配合協作完成。也就是我們通常所說的跨域設置。

The browser adds some additional headers, and sometimes makes additional requests, during a CORS request on behalf of the client

簡單請求

瀏覽器將 CORS 請求分成兩類:簡單請求(simple request)和非簡單請求(not-so-simple request)。

Microsoft API 設計指導 [3] 中這麼一段關於跨域的描述

8.1.1. Avoiding preflight

Because the CORS protocol can trigger preflight requests that add additional round trips to the server, performance-critical apps might be interested in avoiding them.

The spirit behind CORS is to avoid preflight for any simple cross-domain requests that old non-CORS-capable browsers were able to make. All other requests require preflight.

A request is "simple" and avoids preflight if its method is GET, HEAD or POST, and if it doesn't contain any request headers besides Accept, Accept-Language and Content-Language. For POST requests, the Content-Type header is also allowed, but only if its value is "application/x-www-form-urlencoded," "multipart/form-data" or "text/plain." For any other headers or values, a preflight request will happen.

「The spirit behind CORS is to avoid preflight for any simple cross-domain requests that old non-CORS-capable browsers were able to make

我們提煉出簡單請求的判斷標準

1 )GET, HEAD or POST 三種請求 2) 不增加任何額外的請求頭 3) POST 請求允許三種 Content—Type:"application/x-www-form-urlencoded," "multipart/form-data" or "text/plain.

非簡單請求

不符合簡單請求的,均屬於非簡單請求。這麼看來我們平時經常使用的

Content-Type: application/json

顯然是一種非簡單請求頭。

對於非簡單請求,CORS 機制會自動觸發瀏覽器首先進行 preflight(一個 OPTIONS 請求), 該請求成功後才會發送真正的請求。這裡的 preflight 也可以被翻譯為(預檢)請求。

CROS 流程.png

Access-Control-Allow-Headers

像上文中所說那樣, 增加了自定義字段後,跨域請求就變成了一種帶有 preflight 的非簡單請求,因此會有下面的一種理解。

「Access-Control-Allow-Headers 是 preflight 請求中用來標識真正請求將會包含哪些頭部字段,也就是下文中的自定義頭部。這種方式是服務器端安全防範的一種。

Access-Control-Allow-Credentials

這個設置是關於是否支持 Cookies 的

xhr.withCredentials = true;    Access-Control-Allow-Credentials: true

YII2 中的跨域設置

//Access-Control-Allow-Origin:*      public $arr_acao = [          '*'      ];      //Access-Control-Allow-Methods      public $arr_acam = [          'POST',          'PUT',          'GET',          'DELETE',          'OPTIONS'      ];      //Access-Control-Allow-Headers      public $arr_acah = [          'token',          'app-key',          'content-type',      ];     header('Access-Control-Allow-Origin: '.implode(',', $this->arr_acao));          header('Access-Control-Allow-Methods: '.implode(',', $this->arr_acam));          header('Access-Control-Allow-Headers: '.implode(',', $this->arr_acah));          header("Access-Control-Max-Age: 86400");            if ($req->isOptions) {              $code = "202";              $message = "Accepted";              header("HTTP/1.1 ".$code." ".$message);              exit();          }

通過 Access-Control-Allow-Headers 設置自定義字段的方式,是一種安全策略,服務端要求請求頭必須攜帶 'token', 'app-key','content-type'三個屬性字段,缺一不可,否則請求不能達成。

origin 'http://xx.cn' has been blocked by CORS policy:  Request header field timestamp is not allowed by Access-Control-Allow-Headers in preflight response.

Nginx 中的跨域

location ~* .(eot|ttf|woff|woff2|svg)$ {      add_header Access-Control-Allow-Origin *;      add_header Access-Control-Allow-Headers X-Requested-With;      add_header Access-Control-Allow-Methods GET,POST,OPTIONS;  }

以上設置的前提是 Nginix 開啟了模塊 ngx_http_headers_module[4]

Nginx 中的其它 CORS 配置

#  # 用於nginx的開放式CORS配置  #  location / {       if ($request_method = 'OPTIONS') {          add_header 'Access-Control-Allow-Origin' '*';          add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';          #          # 自定義標題和標題各種瀏覽器*應該*可以,但不是          #          add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';          #          # 有效期為          #          add_header 'Access-Control-Max-Age' 22000;          add_header 'Content-Type' 'text/plain; charset=utf-8';          add_header 'Content-Length' 0;          return 204;       }       if ($request_method = 'POST') {          add_header 'Access-Control-Allow-Origin' '*';          add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';          add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';          add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range';       }       if ($request_method = 'GET') {          add_header 'Access-Control-Allow-Origin' '*';          add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';          add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';          add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range';       }

Nginx 中跨域,應用程序與 web 服務器存在耦合,增加了應用程序部署和擴展的複雜性,按需使用。

CROS 總結

本文主要介紹了 CROS 的基本分類和常見的實現方案,對於同源策略,XMLHttpRequest 請求等基礎知識被沒有過多涉及。

簡單請求和非簡單請求的分類是重點。理解了這一點,就能理解什麼場景瀏覽器會發起預檢請求,並回復對應的響應。

我們常說跨域設置是客戶端和服務器端一起配合的結果,官方協議更傾向於讓開發者對於跨域無感知,而瀏覽器與後端服務的交互和相互信任是核心。

參考資料

[1]

Cross-domain Ajax with Cross-Origin Resource Sharing/: https://humanwhocodes.com/blog/2010/05/25/cross-domain-ajax-with-cross-origin-resource-sharing/

[2]

Using CORS: https://www.html5rocks.com/en/tutorials/cors/

[3]

Microsoft API設計指導 : https://github.com/Microsoft/api-guidelines/blob/vNext/Guidelines.md

[4]

ngx_http_headers_module: http://nginx.org/en/docs/http/ngx_http_headers_module.html