Načrtovanje in razvoj spletnih aplikacij

Prijava na zaščitene strani

Pri prijavi uporabnik v obrazec vnese uporabniško ime in geslo. Aplikacija nato preveri, ali tak uporabnik obstaja in ali vneseno geslo ustreza shranjenemu geslu v podatkovni zbirki.

Prijava je občutljiv del spletne aplikacije, ker določa, kdo lahko dostopa do zaščitenih strani. Zato mora biti postopek prijave izveden z varnim shranjevanjem gesel, pripravljenimi stavki, pravilno uporabo sej in splošnimi sporočili ob napakah.

Pomni: Gesel v podatkovni bazi ne shranjujemo v navadnem besedilu. Shranimo zgoščeno vrednost gesla, pri prijavi pa pravilnost preverimo s funkcijo password_verify().

Po uspešni prijavi je priporočljivo izvesti session_regenerate_id(true). S tem zmanjšamo tveganje zlorabe starega identifikatorja seje.

Vsebina strani

Osnovna pravila

Pri prijavi uporabnika preverimo identiteto uporabnika in ob uspehu ustvarimo prijavno stanje v seji. Pri tem ne smemo razkriti, ali je napačno uporabniško ime ali geslo.

  • Prijavni obrazec naj uporablja metodo POST.
  • Uporabniško ime in geslo preberemo iz obrazca.
  • Obe polji morata biti izpolnjeni.
  • Uporabnika poiščemo s pripravljeno poizvedbo.
  • Geslo preverimo s funkcijo password_verify().
  • Po uspešni prijavi regeneriramo ID seje.
  • V $_SESSION shranimo samo potrebne podatke.
  • Uporabnika preusmerimo na zaščiteno stran.

Pozor: Gesla nikoli ne preverjamo z neposredno primerjavo, kot je $vnesenoGeslo === $gesloIzBaze, če je v bazi pravilno shranjen hash. Za preverjanje uporabimo password_verify().

Prijava uporabnika v aplikacijo

Prijava je pomemben del spletne aplikacije, saj z njo omejimo dostop do zaščitenih strani. Po uspešni prijavi uporabnika navadno shranimo v sejo in mu omogočimo dostop do administrativnega ali uporabniškega dela aplikacije.

Pri prijavi običajno izvedemo naslednje korake:

  1. uporabnik izpolni obrazec za prijavo,
  2. aplikacija preveri, ali sta polji izpolnjeni,
  3. aplikacija poišče uporabnika v tabeli users,
  4. preveri se pravilnost gesla,
  5. po uspešni prijavi se nastavijo podatki v seji,
  6. uporabnik se preusmeri na zaščiteno stran.

Gesel v bazi ne shranjujemo v navadnem besedilu, ampak kot varne zgoščene vrednosti. Pri prijavi zato uporabimo funkcijo za preverjanje gesla, ne pa neposredne primerjave z navadnim nizom.

Pomni: Sporočilo ob napačni prijavi naj bo splošno, na primer Uporabniško ime ali geslo ni pravilno. Tako ne razkrivamo, ali uporabniško ime obstaja.

Osnovni primer z mysqli

Spodnji zgled prikaže osnovno prijavo uporabnika z uporabo mysqli, seje in preverjanja gesla s funkcijo password_verify().

<?php
session_start();

define('DB_SERVER', 'localhost');
define('DB_USER', 'uporabnik');
define('DB_PASS', 'skritoGeslo');
define('DB_NAME', 'knjiznica');

$connection = mysqli_connect(DB_SERVER, DB_USER, DB_PASS, DB_NAME);

if (!$connection) {
    die(
        'Povezava s podatkovno zbirko ni vzpostavljena: ' .
        mysqli_connect_error() .
        ' (' . mysqli_connect_errno() . ')'
    );
}

$napaka = '';

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $username = trim($_POST['username'] ?? '');
    $passwordAttempt = trim($_POST['password'] ?? '');

    if ($username === '' || $passwordAttempt === '') {
        $napaka = 'Vpišite uporabniško ime in geslo.';
    } else {
        $stmt = mysqli_prepare(
            $connection,
            "SELECT id, username, password FROM users WHERE username = ?"
        );

        mysqli_stmt_bind_param($stmt, 's', $username);
        mysqli_stmt_execute($stmt);
        $result = mysqli_stmt_get_result($stmt);
        $user = mysqli_fetch_assoc($result);

        if (!$user || !password_verify($passwordAttempt, $user['password'])) {
            $napaka = 'Uporabniško ime ali geslo ni pravilno.';
        } else {
            session_regenerate_id(true);
            $_SESSION['user_id'] = (int)$user['id'];
            $_SESSION['logged_in'] = time();

            header('Location: admin.php');
            exit();
        }

        mysqli_stmt_close($stmt);
    }
}

mysqli_close($connection);
?>

Pri mysqli uporabnika poiščemo s pripravljeno poizvedbo. Vrednost $username je vezana s funkcijo mysqli_stmt_bind_param(), zato je ne vstavljamo neposredno v poizvedbo.

Osnovni primer s PDO

Tudi z vmesnikom PDO prijavo izvedemo s pripravljeno poizvedbo, nato pa geslo preverimo s funkcijo password_verify().

<?php
session_start();

$streznik = 'localhost';
$baza = 'knjiznica';
$uporabnik = 'uporabnik';
$geslo = 'skritoGeslo';

try {
    $pdo = new PDO("mysql:host=$streznik;dbname=$baza;charset=utf8mb4", $uporabnik, $geslo);
    $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

    $napaka = '';

    if ($_SERVER['REQUEST_METHOD'] === 'POST') {
        $username = trim($_POST['username'] ?? '');
        $passwordAttempt = trim($_POST['password'] ?? '');

        if ($username === '' || $passwordAttempt === '') {
            $napaka = 'Vpišite uporabniško ime in geslo.';
        } else {
            $stmt = $pdo->prepare(
                "SELECT id, username, password
                 FROM users
                 WHERE username = :username"
            );
            $stmt->bindValue(':username', $username, PDO::PARAM_STR);
            $stmt->execute();

            $user = $stmt->fetch(PDO::FETCH_ASSOC);

            if (!$user || !password_verify($passwordAttempt, $user['password'])) {
                $napaka = 'Uporabniško ime ali geslo ni pravilno.';
            } else {
                session_regenerate_id(true);
                $_SESSION['user_id'] = (int)$user['id'];
                $_SESSION['logged_in'] = time();

                header('Location: admin.php');
                exit();
            }
        }
    }
}
catch (PDOException $e) {
    $napaka = 'Prijava trenutno ni mogoča.';
}
?>

Pri PDO uporabimo poimenovani parameter :username. Gesla ne iščemo neposredno v poizvedbi, ampak najprej pridobimo shranjeni hash in ga nato preverimo s funkcijo password_verify().

Varno shranjevanje in preverjanje gesel

Gesla se v podatkovni bazi ne smejo shranjevati kot navadno besedilo. Ob registraciji ali ustvarjanju uporabnika geslo pretvorimo v hash s funkcijo password_hash().

$hash = password_hash($geslo, PASSWORD_DEFAULT);

Pri prijavi vnesenega gesla ne hashiramo ročno za neposredno primerjavo. Namesto tega uporabimo funkcijo password_verify(), ki preveri, ali vneseno geslo ustreza shranjeni zgoščeni vrednosti.

if (password_verify($passwordAttempt, $user['password'])) {
    // geslo je pravilno
}
Funkcija Namen
password_hash() Ustvari varno zgoščeno vrednost gesla za shranjevanje v podatkovno bazo.
password_verify() Preveri, ali vneseno geslo ustreza shranjeni zgoščeni vrednosti.
session_regenerate_id(true) Po uspešni prijavi ustvari nov identifikator seje.

Pozor: Tehničnih napak pri prijavi ne prikazujemo neposredno uporabniku. Podrobnosti napake lahko zabeležimo v dnevnik, uporabniku pa pokažemo splošno sporočilo.

Seje in preusmeritev po prijavi

Po uspešni prijavi v sejo shranimo podatke, ki jih aplikacija potrebuje za prepoznavanje prijavljenega uporabnika. Običajno shranimo identifikator uporabnika in čas prijave.

session_regenerate_id(true);
$_SESSION['user_id'] = (int)$user['id'];
$_SESSION['logged_in'] = time();

Po nastavitvi seje uporabnika preusmerimo na zaščiteno stran.

header('Location: admin.php');
exit();

Pomni: Po klicu header('Location: ...') uporabimo exit(), da se izvajanje skripte ustavi.

Primerjava pristopov mysqli in PDO

Oba pristopa omogočata varno prijavo s pripravljenimi stavki. Razlika je predvsem v zapisu poizvedb, vezavi parametrov in obravnavi napak.

Korak mysqli PDO
Priprava poizvedbe mysqli_prepare() $pdo->prepare()
Parameter za uporabniško ime ? :username
Vezava vrednosti mysqli_stmt_bind_param() $stmt->bindValue()
Branje uporabnika mysqli_fetch_assoc() $stmt->fetch()
Preverjanje gesla password_verify() password_verify()

Aplikacija Knjige

📘Aplikacija Knjige

V priloženi aplikaciji je prijava izvedena v datoteki 15_login.php. Datoteka najprej zažene sejo in preveri, ali je uporabnik že prijavljen. Če je, ga takoj preusmeri na administrativno stran.

Ob oddaji obrazca aplikacija prebere uporabniško ime in geslo, nato pa s pripravljeno poizvedbo PDO poišče uporabnika v tabeli users. Če uporabnik ne obstaja ali geslo ni pravilno, se izpiše splošno sporočilo o napačni prijavi.

Po uspešni prijavi aplikacija regenerira ID seje, v sejo shrani identifikator uporabnika in čas prijave, nato pa uporabnika preusmeri na zaščiteno administrativno stran. Na strani sta prikazani tudi povezavi za registracijo in za zamenjavo gesla.

Primer: aplikacija – 15_login.php
  1. Najprej pripravimo obrazec za uporabniško ime in geslo.
  2. Ob oddaji obrazca preberemo oba podatka.
  3. Preverimo, ali sta obe polji izpolnjeni.
  4. S pripravljeno poizvedbo v bazi poiščemo uporabnika po uporabniškem imenu.
  5. Geslo preverimo s funkcijo password_verify().
  6. Ob napačni prijavi uporabniku prikažemo splošno sporočilo.
  7. Po uspešni prijavi regeneriramo ID seje.
  8. V $_SESSION shranimo potrebne podatke.
  9. Uporabnika preusmerimo na zaščiteno stran aplikacije.

Pozor: Pred uporabo funkcije header() ne sme biti izpisa v brskalnik. Če je izpis že izveden, preusmeritev morda ne bo delovala.

Priporočila

  • Gesla shranjuj kot rezultat funkcije password_hash().
  • Pri prijavi geslo preveri s funkcijo password_verify().
  • Za iskanje uporabnika uporabi pripravljen stavek.
  • Po uspešni prijavi uporabi session_regenerate_id(true).
  • V sejo shrani samo nujne podatke.
  • Ob napaki prikaži splošno sporočilo.
  • Po preusmeritvi uporabi exit().
  • Za zaščitene strani preverjaj, ali je uporabnik prijavljen.

Pogoste napake

  • Gesla so shranjena v navadnem besedilu.
  • Geslo se preverja z neposredno primerjavo nizov.
  • Poizvedba za uporabnika ni pripravljena poizvedba.
  • Po uspešni prijavi se ne izvede session_regenerate_id(true).
  • V sejo se shranjuje preveč podatkov.
  • Sporočilo ob napaki razkrije, ali uporabniško ime obstaja.
  • Po klicu header() manjka exit().
  • Zaščitena stran ne preverja, ali je uporabnik prijavljen.

Prijava ni samo preverjanje gesla. Varna prijava vključuje tudi pravilno shranjevanje gesel, pripravljene poizvedbe, varno ravnanje s sejami in nadzor dostopa do zaščitenih strani.