こんにちは、ディレクターのエリカです。
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
の内容で書き換えています。こうすることにより、クライアントのリクエストに応じて、 https
か http
が期待通りに利用されるようになります。
クラウドフロントが
CLOUDFRONT-FORWARDED-PROTO
ヘッダーを送信するようにしておく必要があります。
まとめ
ローカルの開発環境では問題なくても、実際運用する環境でのみ発生する意図しない挙動は、少なくありませんよね。それでもLaravelならエレガントに解決できてしまいます。本当に素晴らしいですね。
LIGはWebサイト制作を支援しています。ご興味のある方は事業ぺージをぜひご覧ください。