🛡️

Laravel で Content-Security-Policy ヘッダーを出力してみる

2022/07/13に公開

前書き

Laravel で Content-Security-Policy ヘッダーを出力してみました、という、平凡な話です。

Content-Security-Policy ヘッダーとは、セキュリティを向上させる為のヘッダーで、例えばインラインで書いた JS などの実行を禁止するようブラウザに伝えたりする為のものです(XSS対策等)。他にも色々ある為、詳しくは「Content-Security-Policy ヘッダー」で検索お願いします。

そもそもヘッダーの出力であれば、Webサーバ側や.htaccess を使えば、簡単にできたりするのですが、Content-Security-Policy(以下、CSP)の場合、「このページでは、こう出力したい!」なんてこともあったりするので、プログラム側で制御できると良い場面があるかと思ったりします。

既に、CSP を管理できるOSSライブラリに spatie/laravel-csp というのがあったりして、これ使えば細かく制御できそうですが、今回は自作で、融通の利かないもの😄をサクッと作ってみたいと思います。とはいえ、ミドルウェア使って、ヘッダーを出力するだけですが😓

本題

ということで、まずはミドルウェアを作成します。下記の感じです。

php artisan make:middleware ContentSecurityPolicy

中身は後で見るとして、まずは web.php のグループとかで、このミドルウェアを指定します。

web.php
<?php

use App\Http\Middleware\ContentSecurityPolicy;
use Illuminate\Support\Facades\Route;

Route::middleware(ContentSecurityPolicy::class)->group(function () {
    Route::get('hoge', fn () => 'hoge');
    Route::get('bar', fn () => 'bar');
    Route::get('baz', fn () => 'baz');
    Route::get('foo', fn () => 'foo')->withoutMiddleware(ContentSecurityPolicy::class);

    // その他
});

これで、hoge, bar, baz の URL の時は、CSP ヘッダーが出力されます。
foo の場合は、withoutMiddleware() しているので、出力されません。(参考までに)

ContentSecurityPolicy ミドルウェアは、例えば以下の感じです。(Docblockは略)

ContentSecurityPolicy.php
<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;

class ContentSecurityPolicy
{
    public function handle(Request $request, Closure $next)
    {
        $response = $next($request);

        if ($response->isServerError()) {
            return $response; // デバッグ画面表示の為
        }

        $csp = <<<'END'
        default-src 'self';
        style-src 'self' 'unsafe-inline' www.example.net;
        img-src 'self' foo.example.com;
        script-src 'self';
        END;

        $csp = preg_replace('~(\s)+~u', ' ', trim($csp)); // スペースを整理して1行に

        $response->headers->set('Content-Security-Policy', $csp);

        return $response;
    }
}

最初の if 文は、デバッグ画面の時には、CSP を出力しない為です。出力すると、デバッグ画面が表示されなくなってしまいます。

そして、正常な時は CSP ヘッダーを出力します。
ヘッダーは1行で書かないといけないのですが、見やすくする為に一度複数行で書き、その後整形して1行にしています。

雑感

とりあえずこんな感じになりました。'self' など以外に 'nonce' とかいうのもあったりして、結構便利です。(今回、割愛してしまいました…)

CSP は、メタタグで記述する事もでき、その方法も場合によってはいいかも知れません。

不具合等ありましたらコメント下さい。

Discussion