62
40

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

LaravelでのCORS対策とmiddlewareへの理解

Last updated at Posted at 2021-04-08

LaravelでCORS対策の勉強をしていくつかの方法を知ったのでCORSへの理解と共にまとめる。

#そもそもCORSとは?
CORSとは Cross-Origin Resource Sharing の略で日本語だとオリジン間リソース共有 (CORS)と言われる。
同一オリジンポリシーを超えて通信を行うことをクロスオリジン間通信といい、その中でリソースの共有、つまりデータを共有することをCORSという。

###オリジンとは何か?
ではオリジンとは何かというと、スキーム + ホスト + ポート番号で構成される組み合わせのことである。
この組み合わせが一致すると同一オリジンと見なされて、一つでも一致しないとクロスオリジンとみなされる。
ドメインと似ているが、プロトコルとポート番号を含んでいる点がドメインとは異なる。
origin.png

アクセス元のオリジンが同一オリジンの場合、レスポンスヘッダの有無や内容にかかわらず通信は成功する。
しかし、クロスオリジン間通信では同一オリジンポリシーという制約が発生する。この設定を調整して、__異なるオリジンへアクセスできるように CORSを設定することをCORS対策__という。

レスポンスを受けるサーバー側ではリクエストを受ける下記レスポンスヘッダによってクロスオリジン通信の可否が左右される。

##シンプルリクエストとプリフライトリクエスト
このクロスオリジン間通信ではシンプルリクエストとプリフライトリクエストというブラウザからのリクエストが飛ばされ、これらがあることでクロスオリジン間通信、つまりブラウザはサーバーからレスポンスを得ることができる。

###シンプルリクエスト
シンプルリクエストであるかどうかは以下の__全ての条件__に当てはまるかどうかである。
シンプルリクエストの時はプリフライトリクエストを引き起こさず、外部との通信が可能である。

  • リクエストのメソッドが GET, POST, HEAD のいずれかに設定されている場合

  • Content-Type ヘッダーが以下の設定である時

  • application / x-www-form-urlencoded

  • multipart / form-data

  • text / plain

  • 特定のHTTPリクエストヘッダが含まれない (詳細はこちら→simple_requests

  • Accept

  • Accept-Language

  • Content-Language

  • DPR

  • Downlink

  • Save-Data

  • Viewport-Width

  • Width

###プリフライトリクエスト
プリフライトリクエストはシンプルリクエストではない時に出るリクエストである。その本リクエストを送信しても安全かどうかを確かめるいわゆる毒味のようなリクエストであり、OPTIONS メソッドを用いた通信で安全かどうかの確認をする。

###通信が失敗するとき・・・
クロスオリジン通信ではレスポンスヘッダの オリジン とリクエスト送信元オリジンが一致しないとブラウザは通信を失敗する。要するに同一オリジンポリシーに反しているということである。この時の多くのパターンとして2パターンある。

1.プリフライトリクエスト(本リクエスト前)により相互のオリジンの不一致が判明し通信が失敗するとき
2.本リクエストを送ったもののオリジンの不一致がわかり、レスポンス受信後に通信が失敗するとき

prerequest.png
response.png

##ここまでで押さえてほしいこと

  • CORSでは通信の可否がレスポンスヘッダで決まるということ
  • ブラウザからのリクエストにも数種類あり、それらもレスポンスヘッダにより通信の可否が決まってしまうということ

要するにバックエンドのレスポンスヘッダの設定でCORS通信の可否が決まるということ!!

今回CORS対策するのはこのレスポンスヘッダでオリジンの不一致を解消して本リクエストを成功させるものである。

#環境
Laravel Framework 8.35.1
ここから実際にLaravelでのCORS対策を紹介する。

なおフロントからのリクエストURLは全て http://127.0.0.1:8000/apiである。番外編のみ http://127.0.0.1:8000/ で送っている。

#1、config/cors.php を用いた方法

Laravel7.0以降からconfig/cors.phpを用いたCORS対策が可能になったと聞く。一般的にLaravelの7.0以降バージョンを使っている場合は cors.phpで書くのがスタンダードであるらしい。

config/cors.php
<?php
return [
  // CORSヘッダーを出力するパスのパターン、任意でワイルドカード(*)が利用できる。
  //全てのルートを対象にする場合: ['*']
  //APIと特定の画像を対象にする例: ['api/*', 'resources/example.png']
 'paths' => ['*'],

  // マッチするHTTPメソッド。 `[*]` だと全てのリクエストにマッチする。
  //GETとPOSTだけを許可する場合: ['GET', 'POST']
 'allowed_methods' => ['*'],

  // 許可するリクエストオリジンの設定
  //`*`かオリジンに完全一致、またはワイルドカードが利用可。
 'allowed_origins' => ['*'],

  //正規表現によるオリジン指定。preg_matchの引数としてそのまま渡される。
 'allowed_origins_patterns' => [],

 // Access-Control-Allow-Headers response header レスポンスヘッダーの指定
 'allowed_headers' => ['*'],

 //Access-Control-Expose-Headers レスポンスヘッダーの指定
 'exposed_headers' => false,

 //Access-Control-Max-Age レスポンスヘッダーの指定
 'max_age' => false,

 // Access-Control-Allow-Credentialsヘッダーを設定する。
 //falsy値を指定すると出力せず、truthyな値を渡せばtrueが出力される
 'supports_credentials' => false,
];

これでどんなURLからのアクセスもリクエストを許可するようになる。
ここでは middleware でも関わってくる重要な3つのレスポンスヘッダについて紹介する。

###Access-Control-Allow-Origin
allowed_originsAccess-Control-Allow-Origin のことであり、 通信を許可する送信元オリジンを指定し、送信元のオリジンがこれに該当するようであれば、クロスオリジンであってもブラウザは通信を許可する。

ちなみにローカルからのアクセスのみに限定する場合はこのように記述する。
'allowed_origins' => ['http://localhost:8080']

###Access-Control-Allow-Methods
allowed_headersAccess-Control-Allow-Methods のことであり、許可するHTTPリクエストのメソッドを指定する。
Preflightリクエストの Access-Control-Request-Method で指定される値が設定されることになる。

###Access-Control-Allow-Headers
許可するHTTPヘッダーを指定する。
PreflightリクエストのAccess-Control-Request-Headersで指定される値が設定されることになる。
Content-Type の指定に関わる。

7.0より前のバージョンだとパッケージをインストールしなければならないらしいが、7.0以上のものは初期状態から備わっているらしい。
おそらく、そのため、このファイルの設定のみでCORSの対策ができる。

#2、middleware を用いた方法
次に、 middleware を用いてCORS対策を行ってみる。
先ほどもお伝えしたように7.0以降のバージョンを用いる場合はこの方法は適切ではない可能性がある。ただ自分自身の middleware への理解もかねてこの方法を利用してみた。
ここではもちろん、config/cors.php がないとい前提である。

まずは、middleware の作成からである。

$ php artisan make:middleware Cors

作成したmiddlewareを以下のように変更した。

Cors.php
<?php

namespace App\Http\Middleware;

use Closure;

class Cors
{
    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @return mixed
     */
    public function handle($request, Closure $next)
    {
        return $next($request)
            ->header('Access-Control-Allow-Origin', '*')
            ->header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS')
            ->header('Access-Control-Allow-Headers', 'Content-Type');
    }
}

api.phpでのルーティング

先ほどの記述で middleware の作成ができたので、 middlewareKarnel.php に登録して、ルーティングに反映させる必要がある。
今回は middleware への理解もかねて3つの方法で middleware を登録してCORS対策をしてみた。

##グローバルミドルウェアへの登録
グローバルミドルウェアに登録することでルーティングに middleware を反映させなくても Cors.php を利用してCORS対策ができる。

Karnel.php
protected $middleware = [
        // \App\Http\Middleware\TrustHosts::class,
        \App\Http\Middleware\TrustProxies::class,
        \Fruitcake\Cors\HandleCors::class,
        \App\Http\Middleware\PreventRequestsDuringMaintenance::class,
        \Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
        \App\Http\Middleware\TrimStrings::class,
        \Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
        'cors' => \App\Http\Middleware\Cors::class, // ここに追加
    ];

ルーティングの変更

api.php
Route::get('/', [TestController::class, 'index']);

グローバルミドルウェアはルーティングに反映させなくても全てのルーティングにその middleware を適用するため、このような記述でCORS対策が可能である。

なお、この時にフロントからバックエンドを叩く時のリクエストURLはhttp://127.0.0.1:8000/apiのように末尾に api をパラメータとして渡さないと api.php からルーティングを探さず、 web.php から探してしまうので注意が必要である。

##グループミドルウェアへの登録
次に、グループミドルウェアに登録してCORS対策を行う。Laravelでは標準的に webapiというグループミドルウェアがあり、api.php でのルーティングでは api のグループミドルウェアが適用されるので、今回はその apiのなかにcorsという middleware を登録してCORS対策を行う。

Karnel.php
 protected $middlewareGroups = [
        'web' => [
            \App\Http\Middleware\EncryptCookies::class,
            \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
            \Illuminate\Session\Middleware\StartSession::class,
            // \Illuminate\Session\Middleware\AuthenticateSession::class,
            \Illuminate\View\Middleware\ShareErrorsFromSession::class,
            \App\Http\Middleware\VerifyCsrfToken::class,
            \Illuminate\Routing\Middleware\SubstituteBindings::class,
        ],

        'api' => [
            'throttle:api',
            \Illuminate\Routing\Middleware\SubstituteBindings::class,
            'cors' => \App\Http\Middleware\Cors::class,// ここに追加
        ],
    ];

これでグループミドルウェアに登録できた。なお、今回は先ほどのルーティングに変更は必要ない。というのも先ほど述べたようにLaravelではapi.php でのルーティングでは api のグループミドルウェアが適用されるので、この apicors 登録することで必然とCORS対策が可能となるのだ。

##ルートミドルウェアの利用
次にルートミドルウェアに登録して、cors.phpを利用する。まずは、いつも通り Karnel.php への登録からだ。

Karnel.php
  protected $routeMiddleware = [
        'auth' => \App\Http\Middleware\Authenticate::class,
        'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
        'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
        'can' => \Illuminate\Auth\Middleware\Authorize::class,
        'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
        'password.confirm' => \Illuminate\Auth\Middleware\RequirePassword::class,
        'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class,
        'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
        'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
        'cors' => \App\Http\Middleware\Cors::class,//ここに追加
    ];

今回はルーティングにも middleware の利用も明示しなければならないのでルーティングにもこのように変更した。

api.php
Route::group(['middleware' => ['cors']], function(){
    Route::options('/', function() {
        return response()->json();
    });
    Route::get('/', [TestController::class, 'index']);
});

また、このようなルーティングでもよい。

api.php
Route::get('/', [TestController::class, 'index'])->middleware('cors');

個別に middleware の登録をしたときは後者のような書き方でも構わないが、共通の middleware を一括して適用する時は Route:group が便利である。

このように middleware への記述と登録、そして、ルーティングでCORS対策を行うことができる。

##番外編web.phpでのルーティング
APIに関する通信は本来、 api.php でルーティングを行うべきであるが今回は実験として、web.php ではどのようにルーティングして動かすこのか挙動を確認した。
例えば、フロントのリクエストURLが http://127.0.0.1:8000 のようなときもバックエンドと通信をしたいとすると、この時Laravelではweb.phpでルーティングを探してしまうためにこの web.php に対してCORS対策をしてAPIを取得しなければならない。

この場合を検討すると主に二つの対策があると考えられる。最初に紹介した config/cors.php を記述する方法である。
この方法はルーティングも特に影響なく、CORS対策ができるから楽だと思われる。
次に後半に紹介した middleware を用いる方法である。この場合は紹介していようにCORS対策用のmiddlewareKarnel.php に登録してルーティングにも反映させなければならない。

例えば、ルートミドルウェアに登録した場合、web.php は以下のようなルーティングをして、middleware を利用する必要がある。

web.php
Route::get('/', [MoviesController::class, 'get'])->middleware('cors');

ルーティングに関しては Route:group を利用しても良いが、いずれにしろ本来APIに関する通信は api.phpに記述するようにAPIを作成する必要がある。

##まとめ
LaravelにおいてCORS対策は config で設定するのが楽ちん! 

##参考
https://qiita.com/hikkappi/items/1b51b9e58e8e391762de

62
40
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
62
40

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?