Увійти Реєстрація
Блог Серії
Кар'єра
Вакансії Компанії
Навчання
Документація Співбесіди Тестування Відео
Екосистема
Пакети Ресурси Проєкти Інструменти Події
Інше
Про нас Реклама

Як захистити завантаження приватних файлів через підписані URL у Laravel?

Проблема: рахунки, договори, вкладення листування лежать у сховищі, а посилання на них мають вигляд /storage/invoices/1041.pdf. Хто знає чи вгадає адресу, той завантажить файл - без перевірки прав.

Перше правило: приватні файли - не в публічній папці (public/, storage/app/public з посиланням storage:link), а на приватному диску. Віддає їх контролер з перевіркою прав:

Route::get('/invoices/{invoice}/download', function (Invoice $invoice) {
    Gate::authorize('download', $invoice);

    return Storage::disk('private')->download($invoice->path, "invoice-{$invoice->number}.pdf");
})->middleware('auth');

Підписані URL - коли файл треба віддати без входу (посилання в листі, вбудування в сторонній сервіс) або на обмежений час:

$url = URL::temporarySignedRoute(
    'invoices.download',
    now()->plus(hours: 24),
    ['invoice' => $invoice->id],
);
Route::get('/invoices/{invoice}/download', DownloadInvoiceController::class)
    ->name('invoices.download')
    ->middleware('signed');
  • до URL додаються expires і signature - HMAC від усієї адреси з ключем застосунку (APP_KEY);
  • зміна будь-якого параметра (invoice=1042) чи терміну дії робить підпис недійсним - 403;
  • після закінчення терміну посилання перестає працювати.

Що важливо розуміти:

  • підписаний URL - це «пред'явницький» доступ: будь-хто з посиланням отримає файл. Переслане, збережене в історії, потрапило в лог - доступ має кожен. Тому: короткий термін дії, а для чутливих документів - підпис плюс перевірка входу й прав;
  • ключ підпису - APP_KEY: його зміна робить недійсними всі видані посилання;
  • за проксі (Cloudflare, балансувальник) адреса, яку бачить Laravel, може відрізнятися від підписаної (схема, хост) - тоді допомагає signed:relative і підпис відносного URL (absolute: false);
  • одноразовість підписані URL не забезпечують - для одноразових посилань потрібна позначка в базі.

Для файлів у S3/R2 аналог - Storage::temporaryUrl(): підписане посилання сховища, і файл віддається напряму з нього, минаючи сервер застосунку.

Докладніше в документації: Laravel: підписані URL

Схожі питання