クリック単価11円の男 / セールス
クリック単価11円の男 / セールス
2020.01.23
#58
バックエンドへの道

Amazon CloudFrontを経由したLaravelがHTTPSにならないとき

エリカ

こんにちは、ディレクターのエリカです。

Laravelをロードバランサなどの後ろで利用する際に、クライアントからのリクエストヘッダーを正しく処理できない場合があります。

LaravelにはTrustProxiesミドルウェアが、標準で動作するようになっており、正しいところからのリクエストのみを信頼して処理しているからです。

この仕組みを簡単に紹介すると、まず、リクエスト元のIPアドレスが、所定のものかどうかを調べ、さらにその場合でも、所定のリクエストヘッダーのみを信頼して利用するようになっています。

そこで今回は、AWSCloudFrontとロードバランサを経由した場合に、LaravelがクライアントのリクエストがHTTPSかどうかを正しく判定できない場合の対応についてご紹介します。

プロキシのIP指定を外す

最終的なLaravelへのリクエストはロードバランサから行われます。そこで、まずはIPアドレスの指定部分に対応します。

<?php

namespace App\Http\Middleware;

use Illuminate\Http\Request;
use Fideloper\Proxy\TrustProxies as Middleware;

class TrustProxies extends Middleware
{
    /**
     * このアプリケーションで信用するプロキシ
     *
     * @var array
     */
    protected $proxies = [
        '192.168.1.1',
        '192.168.1.2',
    ];

    /**
     * プロキシを検出するために使用するヘッダ
     *
     * @var string
     */
    protected $headers = Request::HEADER_X_FORWARDED_ALL;
}

ロードバランサのIPアドレスを固定できない状態を許容するため、この $proxies プロパティに下記のようなワイルドカードを設定します。

/**
     * The trusted proxies for this application.
     *
     * @var array|string
     */
protected $proxies = '*';

これで、まずはどこからのリクエストでも正しいものとして受け入れます。

セキュリティグループの指定などにより、Laravelへの接続がロードバランサからのみに制限されている場合であれば、この状態でも問題ないかと思います。

信頼するヘッダーについて

次に、クライアントのリクエストがHTTPSかどうかを判定できるようにします。ここはおそらく初期状態のままでも問題ないかと思いますが、たとえば X-FORWORDED-PROTO なリクエストヘッダーのみを利用したい場合は、下記のように指定します。

/**
     * The headers that should be used to detect proxies.
     *
     * @var int
     */
    protected $headers = Request::HEADER_X_FORWARDED_PROTO;

こうすることにより、X-FORWORDED-PROTO リクエストヘッダーの内容によって、 https http で処理すべきかをLaravelが判断してくれるようになります。

具体的には、 request()->isSecure() の真偽値が切り替わり、絶対URLを利用するような場面で、結果が変わってきます。

意図した結果にならない場合

クラウドフロントとロードバランサ間のポートが80か443か、どちらの通信設定にしているかでここの結果が変わってくるかもしれません。

クラウドフロントからロードバランサ間のポートが80番の場合、Laravelには X-FORWORDED-PROTO の内容は http で通知されてしまいます。

この場合は、下記のようなミドルウェアを作成し、 TrustProxies の後ろに差し込みます。

<?php

namespace App\Http\Middleware;

use Closure;

class TrustCloudfrontProxies
{
    public function handle($request, Closure $next)
    {
        if ($request->header('cloudfront-forwarded-proto')) {
            $headers = $request->headers;
            $headers->add(['x-forwarded-proto' => $headers->get('cloudfront-forwarded-proto')]);
        }
        return $next($request);
    }
}

続いて Kernel.php では、このように指定します。

protected $middleware = [
    // 略
    \App\Http\Middleware\TrustProxies::class,
    \App\Http\Middleware\TrustCloudfrontProxies::class,
];

こうすることにより、 X-FORWORDED-PROTO の内容を、クラウドフロントから付与されるヘッダー CLOUDFRONT-FORWARDED-PROTO の内容で書き換えています。こうすることにより、クライアントのリクエストに応じて、 httpshttp が期待通りに利用されるようになります。

クラウドフロントが CLOUDFRONT-FORWARDED-PROTO ヘッダーを送信するようにしておく必要があります。

まとめ

ローカルの開発環境では問題なくても、実際運用する環境でのみ発生する意図しない挙動は、少なくありませんよね。それでもLaravelならエレガントに解決できてしまいます。本当に素晴らしいですね。