Statyczne serwowanie plików w PHP i zabezpieczanie przed bezpośrednim dostępem
Wprowadzenie
W wielu aplikacjach webowych zachodzi potrzeba przechowywania i udostępniania plików użytkownikom — mogą to być zdjęcia profilowe, dokumenty, raporty czy prywatne multimedia. Często chcemy jednak, aby dostęp do tych plików był ograniczony, np. tylko dla zalogowanego użytkownika lub tylko poprzez określone warunki. W tym kontekście PHP może być pomocne nie tylko jako język do obsługi logiki serwera, ale także jako pośrednik w bezpiecznym serwowaniu plików.
Czym jest statyczne serwowanie plików?
Statyczne serwowanie plików polega na udostępnianiu zasobów (takich jak obrazy, pliki PDF, pliki wideo) bezpośrednio przez serwer HTTP, np. Apache lub Nginx, bez udziału kodu aplikacji. Taka metoda jest wydajna, ale ma jedną istotną wadę — brak kontroli dostępu.
Przykład:
Plik znajduje się w katalogu /uploads/public/image.jpg
, a jego adres to:
https://example.com/uploads/public/image.jpg
Każdy, kto zna ten adres, może uzyskać do niego dostęp — niezależnie od uprawnień.
Problem: Jak chronić pliki przed nieautoryzowanym dostępem?
Jeśli plik powinien być dostępny tylko po spełnieniu określonych warunków (np. tylko po zalogowaniu), bezpośredni dostęp przez przeglądarkę jest niepożądany. W takich przypadkach warto zastosować pośrednie serwowanie plików przez PHP.
Metoda: Serwowanie plików przez PHP
Zamiast udostępniać pliki bezpośrednio, możemy przechowywać je poza publicznym katalogiem WWW, a dostęp do nich realizować przez specjalny skrypt PHP.
Struktura katalogów:
/app
/uploads (poza katalogiem publicznym)
/public_html
index.php
serve.php
serve.php
– przykład kodu:
<?php
session_start();
// Sprawdź uprawnienia użytkownika
if (!isset($_SESSION['user_id'])) {
http_response_code(403);
exit('Brak dostępu.');
}
// Pobierz nazwę pliku z parametru GET
$filename = basename($_GET['file'] ?? '');
// Ścieżka do chronionego pliku
$filepath = __DIR__ . '/../uploads/' . $filename;
// Sprawdź, czy plik istnieje
if (!file_exists($filepath)) {
http_response_code(404);
exit('Plik nie istnieje.');
}
// Wysyłanie nagłówków
header('Content-Type: ' . mime_content_type($filepath));
header('Content-Length: ' . filesize($filepath));
header('Content-Disposition: inline; filename="' . $filename . '"');
readfile($filepath);
exit;
Przykładowe wywołanie:
https://example.com/serve.php?file=zdjecie123.jpg
Przykład w Koseven Framework
Poniżej znajdziesz przykład bezpiecznego serwowania plików z użyciem frameworka Koseven (fork Kohana 3.3) oraz metody $response->send_file()
.
Scenariusz
Chcemy udostępnić użytkownikowi plik (np. zdjęcie lub PDF) tylko jeśli jest zalogowany. Pliki są przechowywane poza katalogiem DOCROOT
, np. w APPPATH.'/uploads/'
.
Przykład kontrolera: Controller_File
<?php defined('SYSPATH') or die('No direct script access.');
class Controller_File extends Controller {
public function action_view()
{
// 1. Autoryzacja użytkownika (przykład)
if (!Auth::instance()->logged_in()) {
throw HTTP_Exception::factory(403, 'Brak dostępu');
}
// 2. Pobierz nazwę pliku z GET lub segmentu URI
$filename = basename($this->request->param('file')); // np. /file/view/plik.jpg
// 3. Ścieżka do chronionego pliku
$filepath = APPPATH.'uploads'.DIRECTORY_SEPARATOR.$filename;
// 4. Sprawdzenie istnienia pliku
if (!file_exists($filepath)) {
throw HTTP_Exception::factory(404, 'Plik nie istnieje');
}
// 5. Opcjonalna walidacja uprawnień do danego pliku (np. czy użytkownik jest właścicielem)
// 6. Serwowanie pliku jako inline (czyli do podglądu w przeglądarce, np. PDF lub obraz)
$this->response->send_file($filepath, null, ['inline' => true]);
// Uwaga: send_file automatycznie kończy wykonanie aplikacji
}
}
Routing (w APPPATH/bootstrap.php
lub routes.php
)
Route::set('file.view', 'file/view/<file>')
->defaults([
'controller' => 'file',
'action' => 'view',
]);
Przykładowy URL:
https://example.com/file/view/dokument.pdf
Uwagi dotyczące bezpieczeństwa
basename()
chroni przed atakiem directory traversal (../../etc/passwd
).- Jeśli potrzebujesz przechowywać pliki w strukturze podkatalogów, rozbuduj walidację ścieżki.
-
Można też zastosować token w URL-u, aby ograniczyć dostęp tymczasowo:
file/view/plik.pdf?token=abc123
I sprawdzać token w bazie danych lub sesji.
Zalety send_file()
w Koseven
- Automatycznie ustawia nagłówki
Content-Type
,Content-Disposition
iContent-Length
. - Obsługuje zarówno
inline
, jak iattachment
. - Obsługuje force-download i możliwość cache’owania przez przeglądarkę.
Zalety serwowania plików przez PHP
- Kontrola dostępu – można łatwo sprawdzić, czy użytkownik ma prawo do danego pliku.
- Logowanie dostępu – możliwość zapisywania, kto i kiedy pobrał dany plik.
- Ochrona lokalizacji plików – ścieżka do fizycznego pliku nie jest publiczna.
- Dynamiczne przetwarzanie – np. konwersja obrazów, znak wodny, optymalizacja przed wysłaniem.
Wady i problemy
- Niższa wydajność – PHP musi przetworzyć każde żądanie, co obciąża serwer bardziej niż serwowanie statyczne przez Apache/Nginx.
- Złożoność – dodatkowa warstwa logiki, która wymaga odpowiedniego zabezpieczenia.
- Problemy z cache’owaniem – trudniej zastosować mechanizmy CDN i cache przeglądarki.
- Potencjalne luki – nieprawidłowe filtrowanie nazw plików może prowadzić do ataków typu directory traversal (np.
../../../etc/passwd
).
Dodatkowe metody zabezpieczeń
1. Pliki .htaccess (dla Apache)
W katalogu z plikami można dodać:
Deny from all
To uniemożliwia bezpośredni dostęp przez przeglądarkę.
2. Tokeny dostępu
Generowanie jednorazowych tokenów lub tymczasowych URL-i (np. z datą ważności).
3. Nagłówki referera
Ograniczenie dostępu tylko jeśli żądanie pochodzi z określonej strony (łatwe do obejścia, ale może być użyte jako dodatkowa warstwa).
Podsumowanie
Bezpieczne serwowanie plików to ważny aspekt każdej aplikacji webowej. Statyczne udostępnianie zasobów jest szybkie, ale nie daje kontroli nad tym, kto je pobiera. Dzięki użyciu PHP jako pośrednika można skutecznie ograniczyć dostęp do plików tylko dla uprawnionych użytkowników, choć kosztem wydajności i prostoty.
Dobrze zaprojektowany system serwowania plików powinien łączyć mechanizmy ochrony (autoryzacja, ograniczenie katalogów, walidacja nazw plików) z rozsądnym podejściem do wydajności — np. poprzez cachowanie czy użycie CDN dla mniej wrażliwych danych.