
Izrada sigurnog sustava za prijavu korisnika u PHP-u: Detaljan vodič s najboljim praksama
Sustav za prijavu korisnika (login sustav) temelj je gotovo svake moderne web aplikacije. On omogućuje personalizaciju korisničkog iskustva, štiti osjetljive podatke i kontrolira pristup različitim dijelovima aplikacije. Bez obzira gradite li blog, e-trgovinu ili složenu poslovnu platformu, implementacija robusnog i sigurnog sustava za prijavu je neophodna. Ovaj detaljni vodič provest će vas kroz sve ključne korake izrade takvog sustava koristeći PHP, s posebnim naglaskom na sigurnosne najbolje prakse.
Od postavljanja baze podataka i kreiranja obrasca za prijavu, preko validacije unosa i sigurne autentifikacije lozinke, pa sve do upravljanja korisničkim sesijama, pokrit ćemo sve aspekte potrebne za izgradnju funkcionalnog i pouzdanog login sustava. Cilj je osigurati da vaša aplikacija ne samo radi, već i da štiti podatke vaših korisnika na najvišoj mogućoj razini.
Priprema baze podataka za korisničke račune
Prvi korak u izgradnji sustava za prijavu je stvaranje odgovarajuće strukture u bazi podataka koja će pohranjivati korisničke podatke. Najvažniji podaci su korisničko ime i lozinka, ali često se dodaju i e-mail adresa, datum registracije i slično. Ključno je zapamtiti da se lozinke nikada ne smiju pohranjivati u običnom tekstualnom obliku. Umjesto toga, koristit ćemo funkciju za raspršivanje (hashing) lozinki.
Struktura tablice ‘users’
Predlažemo tablicu pod nazivom users sa sljedećim poljima:
- ID: Primarni ključ, automatski se povećava (
INT,AUTO_INCREMENT). - username: Jedinstveno korisničko ime (
VARCHAR(50),UNIQUE). - email: E-mail adresa korisnika (
VARCHAR(100),UNIQUE, opcionalno). - password: Raspršena (hashed) lozinka (
VARCHAR(255)). Važno je da bude dovoljno dugačka za pohranu raspršenog niza. - created_at: Vremenska oznaka kreiranja računa (
TIMESTAMP,DEFAULT CURRENT_TIMESTAMP).
SQL upit za kreiranje tablice
Sljedeći SQL upit možete izvršiti u phpMyAdminu ili putem MySQL klijenta:
CREATE TABLE IF NOT EXISTS `users` ( `ID` INT(11) NOT NULL AUTO_INCREMENT, `username` VARCHAR(50) NOT NULL UNIQUE, `email` VARCHAR(100) NOT NULL UNIQUE, `password` VARCHAR(255) NOT NULL, `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`ID`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;Pohrana lozinki: Hashing s password_hash()
PHP nudi ugrađenu funkciju password_hash() koja sigurno raspršuje lozinke. Ova funkcija koristi snažne, prilagodljive algoritme (poput bcrypta) i automatski generira “sol” (salt) za svaku lozinku, što je ključno za sprječavanje rainbow table napada. Nikada nemojte koristiti MD5, SHA1 ili slične zastarjele funkcije za heširanje lozinki.
Primjer umetanja testnog korisnika s raspršenom lozinkom:
<?php
$testPassword = 'testPassword123';
$hashedPassword = password_hash($testPassword, PASSWORD_DEFAULT); // Sada možete koristiti $hashedPassword u SQL upitu
// Npr. INSERT INTO `users` (username, email, password) VALUES ('testUser', '[email protected]', '$hashedPassword');
?>Kreiranje obrasca za prijavu
Korisnicima je potreban način za unos svojih vjerodajnica. To se obično postiže HTML obrascem koji šalje podatke na PHP skriptu. Kreirajmo jednostavan login.php datoteku s osnovnom HTML strukturom:
<!DOCTYPE html>
<html lang="hr">
<head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Prijava korisnika</title> <style> body { font-family: Arial, sans-serif; background-color: #f4f4f4; display: flex; justify-content: center; align-items: center; height: 100vh; margin: 0; } .login-container { background-color: #fff; padding: 30px; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); width: 300px; } h2 { text-align: center; margin-bottom: 20px; color: #333; } label { display: block; margin-bottom: 8px; color: #555; } input[type="text"], input[type="password"] { width: calc(100% - 20px); padding: 10px; margin-bottom: 15px; border: 1px solid #ddd; border-radius: 4px; } input[type="submit"] { width: 100%; padding: 10px; background-color: #007bff; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 16px; } input[type="submit"]:hover { background-color: #0056b3; } .error-message { color: red; text-align: center; margin-bottom: 15px; } </style>
</head>
<body> <div class="login-container"> <h2>Prijava</h2> <form action="" method="post"> <label for="username">Korisničko ime:</label> <input type="text" id="username" name="username" required> <label for="password">Lozinka:</label> <input type="password" id="password" name="password" required> <input type="submit" name="submit_login" value="Prijavi se"> </form> </div>
</body>
</html>Atribut action="" znači da će se forma poslati na istu stranicu (login.php), dok method="post" osigurava da se podaci šalju u tijelu HTTP zahtjeva, a ne u URL-u, što je sigurnije za osjetljive podatke poput lozinki.
Obrada i validacija korisničkih unosa na strani poslužitelja
Nakon što korisnik pošalje obrazac, PHP skripta mora preuzeti, očistiti i validirati unesene podatke. Validacija je ključna za sprječavanje ranjivosti i osiguravanje ispravnosti podataka.
Dohvaćanje i čišćenje podataka
Umetnite sljedeći PHP kod na početak datoteke login.php, prije HTML koda:
<?php
session_start(); // Pokreće sesiju // Provjeravamo je li korisnik već prijavljen
if (isset($_SESSION['user_id'])) { header('Location: dashboard.php'); // Preusmjeri na administratorsku stranicu exit();
} $errors = []; // Prazno polje za pohranu pogrešaka if (isset($_POST['submit_login'])) { $username = trim($_POST['username']); $password = trim($_POST['password']); // Sanitizacija unosa za prikaz (iako se lozinka ne prikazuje, dobra je praksa) $username = htmlspecialchars($username, ENT_QUOTES, 'UTF-8'); // Lozinka se ne sanitizira s htmlspecialchars jer se provjerava raspršena vrijednost // Validacija korisničkog imena if (empty($username)) { $errors[] = "Korisničko ime je obavezno."; } elseif (!preg_match('/^[a-zA-Z0-9_]{5,}$/', $username)) { $errors[] = "Korisničko ime mora imati najmanje 5 znakova i sadržavati samo slova, brojeve i podcrtu."; } // Validacija lozinke if (empty($password)) { $errors[] = "Lozinka je obavezna."; } elseif (strlen($password) < 8) { $errors[] = "Lozinka mora imati najmanje 8 znakova."; } // Prikaz pogrešaka ako postoje if (!empty($errors)) { echo '<div class="error-message"><ul>'; foreach ($errors as $error) { echo '<li>' . $error . '</li>'; } echo '</ul></div>'; } else { // Ovdje ide logika autentifikacije, ako nema pogrešaka // (Nastavak u sljedećem poglavlju) }
}
?>Objašnjenje validacije
trim(): Uklanja prazne znakove s početka i kraja stringa.htmlspecialchars(): Pretvara posebne znakove u HTML entitete, sprječavajući Cross-Site Scripting (XSS) napade kada se podaci kasnije prikazuju.preg_match(): Koristi regularne izraze za provjeru formata korisničkog imena. U ovom slučaju, zahtijeva najmanje 5 alfanumeričkih znakova ili podcrtu.strlen(): Provjerava minimalnu duljinu lozinke.$errorspolje: Pohranjuje sve pronađene pogreške validacije, koje se zatim prikazuju korisniku.
Sigurna autentifikacija korisnika
Nakon što su podaci validirani, potrebno je provjeriti postoji li korisnik s unesenim korisničkim imenom u bazi podataka i odgovara li unesena lozinka pohranjenoj raspršenoj lozinci. U ovom primjeru, koristit ćemo pristup sličan onom iz originalnog članka, pretpostavljajući korištenje Active Record biblioteke za interakciju s bazom, ali s ključnim izmjenama za sigurnost.
Model korisnika s Active Recordom
Ako koristite Active Record, vaš model User mogao bi izgledati ovako (pretpostavljajući da je Active Record već konfiguriran):
<?php
// U datoteci gdje su definirani vaši modeli, npr. 'models/User.php'
class User extends ActiveRecord\Model
{ static $table_name = 'users'; static $primary_key = 'ID'; // Povezivanje na bazu podataka ovisi o vašoj konfiguraciji Active Recorda // static $connection = 'development'; // static $db = 'blog';
}
?>Provjera vjerodajnica
Zamijenite komentar // Ovdje ide logika autentifikacije... u vašoj login.php datoteci sa sljedećim kodom:
<?php
// ... (prethodni kod za validaciju) } else { // Nema pogrešaka u validaciji, pokušaj autentifikacije require_once 'path/to/ActiveRecord.php'; // Uključite Active Record biblioteku // Ovdje je potrebno uspostaviti vezu s bazom podataka za Active Record // Npr. ActiveRecord\Config::initialize(function($cfg) // { // $cfg->set_model_directory('models'); // $cfg->set_connections(array( // 'development' => 'mysql://user:password@localhost/blog' // )); // }); $user = User::find_by_username($username); if ($user && password_verify($password, $user->password)) { // Korisnik je uspješno prijavljen $_SESSION['user_id'] = $user->ID; $_SESSION['username'] = $user->username; header('Location: dashboard.php'); // Preusmjeri na administratorsku stranicu exit(); } else { $errors[] = "Pogrešno korisničko ime ili lozinka."; echo '<div class="error-message"><ul>'; foreach ($errors as $error) { echo '<li>' . $error . '</li>'; } echo '</ul></div>'; } }
}
?>Ovdje je ključna funkcija password_verify($password, $user->password). Ona uspoređuje unesenu lozinku s raspršenom lozinkom pohranjenom u bazi. Važno je da se prvo dohvati korisnik po korisničkom imenu, a tek onda provjerava lozinka. Poruka o pogrešci treba biti generička (“Pogrešno korisničko ime ili lozinka”) kako ne bi napadačima davala informacije o tome postoji li korisničko ime u sustavu.
Upravljanje korisničkim sesijama za održavanje prijave
Nakon uspješne prijave, potrebno je korisnika držati prijavljenim dok se kreće po web stranici. To se postiže korištenjem PHP sesija. Sesije omogućuju pohranu korisničkih podataka na serveru i povezivanje tih podataka s korisnikom putem jedinstvenog ID-a sesije, koji se obično pohranjuje u kolačiću na klijentskoj strani.
Pokretanje sesije
Funkcija session_start() mora biti pozvana na samom početku svake PHP skripte koja koristi sesije, prije bilo kakvog HTML izlaza. Već smo je dodali na početak login.php.
Pohrana podataka u sesiju
Nakon uspješne prijave, pohranjujemo relevantne korisničke podatke (npr. user_id, username) u globalno polje $_SESSION:
<?php
// ... unutar bloka za uspješnu prijavu ... $_SESSION['user_id'] = $user->ID; $_SESSION['username'] = $user->username; header('Location: dashboard.php'); // Preusmjeri korisnika exit();
// ...
?>Provjera prijave na zaštićenim stranicama
Na svim stranicama koje zahtijevaju prijavu (npr. dashboard.php), trebali biste provjeriti je li korisnik prijavljen. Ako nije, preusmjerite ga natrag na stranicu za prijavu:
<?php
session_start(); if (!isset($_SESSION['user_id'])) { header('Location: login.php'); exit();
} // Ako je korisnik prijavljen, možete pristupiti njegovim podacima
$loggedInUserId = $_SESSION['user_id'];
$loggedInUsername = $_SESSION['username']; // Ostatak koda za dashboard.php
?>Odjava korisnika
Za odjavu korisnika, jednostavno uništite sesiju. To se obično radi na stranici logout.php:
<?php
session_start(); // Uništi sve varijable sesije
$_SESSION = array(); // Ako se koristi sesijski kolačić, obrišite ga
if (ini_get("session.use_cookies")) { $params = session_get_cookie_params(); setcookie(session_name(), '', time() - 42000, $params["path"], $params["domain"], $params["secure"], $params["httponly"] );
} // Konačno, uništi sesiju
session_destroy(); header('Location: login.php'); // Preusmjeri na stranicu za prijavu
exit();
?>Zaključak
Izgradnja sigurnog sustava za prijavu korisnika u PHP-u zahtijeva pažljivo planiranje i implementaciju najboljih praksi. Kroz ovaj vodič, prošli smo kroz ključne korake: od postavljanja sigurne baze podataka s heširanim lozinkama, preko kreiranja funkcionalnog obrasca za prijavu i detaljne validacije unosa, do sigurne autentifikacije korisnika pomoću password_verify() i efikasnog upravljanja sesijama. Pridržavanje ovih smjernica osigurat će da vaša aplikacija bude robustna, pouzdana i otporna na uobičajene sigurnosne prijetnje, pružajući vašim korisnicima sigurno i ugodno iskustvo.
Često postavljana pitanja (FAQ)
- Zašto koristiti
password_hash()umjestomd5()ilisha1()? password_hash()koristi moderne, snažne algoritme (poput bcrypta) dizajnirane specifično za heširanje lozinki. Oni su otporni na brute-force i rainbow table napade jer su spori, automatski generiraju sol (salt) i koriste prilagodljiv faktor rada.md5()isha1()su brzi, ne generiraju sol i stoga su izuzetno nesigurni za lozinke.- Što je
session_start()i gdje ga trebam postaviti? session_start()inicijalizira sesiju ili nastavlja postojeću sesiju. Omogućuje vam pohranu korisničkih podataka na poslužitelju (u$_SESSIONsuperglobalnoj varijabli) koji su dostupni na više stranica. Mora se pozvati na samom početku svake PHP skripte koja koristi sesije, prije bilo kakvog HTML izlaza ili ispisa na ekran.- Kako spriječiti SQL Injection?
- Najbolji način za sprječavanje SQL Injectiona je korištenje pripremljenih izjava (prepared statements) s parametrima. Biblioteke poput Active Recorda (koju smo ovdje pretpostavili) ili korištenje PDO/MySQLi ekstenzija s pripremljenim izjavama automatski se brinu o tome. Nikada nemojte izravno umetati korisnički unos u SQL upite.
- Kako osigurati login formu od XSS napada?
- XSS (Cross-Site Scripting) napadi događaju se kada napadač ubaci zlonamjerni skript u web stranicu. Da biste to spriječili, uvijek koristite
htmlspecialchars()ili slične funkcije za sanitizaciju svih korisničkih unosa prije nego što ih prikažete na stranici. Time se posebni HTML znakovi pretvaraju u entitete, sprječavajući izvršavanje skripti.