CORS的簡述及Yii2設定

cors的機制是,當跨網域透過ajax要求資料時,瀏覽器會先向來源網站詢問是否允許跨網域要求資料,此為prelight request,只回傳相關headers,不輸出任何內容。

如果來源網站有回應cors相關header時,並且ajax request符合headers規範的條件,瀏覽器就會放行連線。若來源網站沒有回應cors相關headers時,視為不允許跨網域連線。

這限制的執行是由瀏覽器端來負責,若瀏覽器不實作cors機制,就算來源網站有正常回覆 cors headers也不會有效果。

所以瀏覽器在實作ajax request時,實際會有兩個request,一個是預先詢問的options request (prelight request),後面才是實際的request。

當你實作 yii\rest\Controller 或 yii\rest\ActiveController時,yii2官方建議 authenticator filter 需移到 cors filter 之後,實際上 verb filter 可能也需要移到 cors filter 之後,如下段敘述所示。

 public function behaviors()
    {
        $behaviors = parent::behaviors();

        // remove authentication filter
        $auth = $behaviors['authenticator'];
        unset($behaviors['authenticator']);
        // remove verb filter
        $verb = $behaviors['verbFilter'];
        unset($behaviors['verbFilter']);

        // add CORS filter
        $behaviors['corsFilter'] = [
            'class' => \yii\filters\Cors::class,
        ];
        // re-add verb filter
        $behaviors['verbFilter'] = $verb;

        // re-add authentication filter
        $behaviors['authenticator'] = $auth;
        // avoid authentication on CORS-pre-flight requests (HTTP OPTIONS method)
        $behaviors['authenticator']['except'] = ['options'];

        return $behaviors;
    }

因為基礎類別的behaviors並不包括cors filter,因此在繼承類別設定cors filters時,其執行順序會晚於verb filter,而按預設verb filter設定,options是會被擋掉的。

    /**
     * {@inheritdoc}
     */
    public function behaviors()
    {
        return [
            'contentNegotiator' => [
                'class' => ContentNegotiator::className(),
                'formats' => [
                    'application/json' => Response::FORMAT_JSON,
                    'application/xml' => Response::FORMAT_XML,
                ],
            ],
            'verbFilter' => [
                'class' => VerbFilter::className(),
                'actions' => $this->verbs(),
            ],
            'authenticator' => [
                'class' => CompositeAuth::className(),
            ],
            'rateLimiter' => [
                'class' => RateLimiter::className(),
            ],
        ];
    }

基礎類別的behaviors

    /**
     * {@inheritdoc}
     */
    protected function verbs()
    {
        return [
            'index' => ['GET', 'HEAD'],
            'view' => ['GET', 'HEAD'],
            'create' => ['POST'],
            'update' => ['PUT', 'PATCH'],
            'delete' => ['DELETE'],
        ];
    }

預設verb設定。

這裡有兩個作法,一個即是前述將 verb filter 移到 cors filter 之後。

另外一個就是在verb設定裡,將會產生options prelight request的method裡再加上options,實測會產生options的method應該就是非get、post的method,因此若採用第二種方法,put、 patch、 delete等method都要再加上options,才能通過verb filter。參閱簡易請求

    protected function verbs()
    {
        return [
            'index' => ['GET', 'HEAD'],
            'view' => ['GET', 'HEAD'],
            'create' => ['POST'],
            'update' => ['PUT', 'PATCH','OPTIONS'],
            'delete' => ['DELETE','OPTIONS'],
        ];
    }

另外,cors filter預設設定會讓ajax處理回傳值時無法取得所有headers。下列圖示由左至右,分別是chrome實際記錄的ajax response headers, ajax呼叫敘述以及敘述裡取得的所有headers輸出。看得出來ajax回傳結果能處理的headers少於實際的headers。

這時可以針對指定action再覆寫預設cors設定,如下段敘述所示。這樣 Access-Control-Expose-Headers 裡指定的header就會出現在 xhr的getAllResponseHeaders裡面了。

        // add CORS filter
        $behaviors['corsFilter'] = [
            'class' => \yii\filters\Cors::class,
            //覆寫預設的 Access-Control-Expose-Headers
            'actions' => [
                'index' => [
                    'Access-Control-Expose-Headers' => [
                        'X-Pagination-Current-Page',
                        'X-Pagination-Page-Count',
                        'X-Pagination-Per-Page',
                        'X-Pagination-Total-Count',
                    ],
                ],
            ],
        ];

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *

Proudly using Dynamic Headers by Nicasio WordPress Design