Načrtovanje in razvoj spletnih aplikacij

Zaščita strani

Zaščita strani pomeni, da do določenih delov aplikacije lahko dostopajo samo prijavljeni uporabniki. Takšne strani navadno vsebujejo občutljive podatke ali pa omogočajo spreminjanje podatkov v bazi.

Zaščita strani ni samo skrivanje povezav v meniju. Prava zaščita mora biti izvedena v PHP kodi na začetku vsake zaščitene datoteke. Če uporabnik ni prijavljen, se izvajanje strani prekine in uporabnik se preusmeri na prijavno stran.

Pomni: Vsaka zaščitena stran mora sama preveriti prijavo uporabnika. Ni dovolj, da zaščito izvedemo samo na administrativni strani ali samo v navigaciji.

Zaščito strani običajno izvedemo s sejo. Pri prijavi v $_SESSION shranimo podatke o uporabniku, na zaščitenih straneh pa preverimo, ali ti podatki obstajajo.

Vsebina strani

Osnovna pravila

Zaščitena stran mora preveriti prijavo še pred prikazom vsebine, pred odpiranjem administrativnih obrazcev in pred izvajanjem občutljivih dejanj.

  • Na začetku datoteke uporabimo session_start().
  • Preverimo, ali v $_SESSION obstajajo podatki o prijavi.
  • Če uporabnik ni prijavljen, ga preusmerimo na 15_login.php.
  • Po preusmeritvi uporabimo exit().
  • Šele po uspešnem preverjanju prikažemo zaščiteno vsebino.
  • Za obrazce, ki spreminjajo podatke, uporabimo CSRF zaščito.
  • Za brisanje in podobna dejanja uporabimo metodo POST.
  • Vhodne podatke vedno preverimo na strežniku.

Pozor: Če zaščitena datoteka ne preveri prijave, jo lahko uporabnik odpre neposredno z vpisom naslova v brskalnik, tudi če povezava do nje ni prikazana v meniju.

Zaščita strani v spletni aplikaciji

Če želimo preprečiti dostop nepooblaščenim obiskovalcem, moramo na začetku zaščitene strani preveriti, ali je uporabnik uspešno prijavljen. To najpogosteje naredimo s pomočjo seje.

Osnovna logika zaščite strani je:

<?php
session_start();

if (!isset($_SESSION['user_id'], $_SESSION['logged_in'])) {
    header('Location: 15_login.php');
    exit();
}
?>

Če podatki o prijavi v seji ne obstajajo, uporabnika preusmerimo na prijavno stran. Če obstajajo, lahko nadaljuje delo na zaščiteni strani.

Zaščita strani je posebej pomembna pri:

  • administrativnih straneh,
  • obrazcih za dodajanje zapisov,
  • obrazcih za urejanje zapisov,
  • datotekah za brisanje zapisov,
  • straneh z osebnimi ali občutljivimi podatki.

Pomni: Zaščito strani izvedemo pred izpisom HTML kode. Tako lahko uporabimo header() za preusmeritev neprijavljenega uporabnika.

Osnovni primer z mysqli

Spodnji zgled prikazuje klasično zaščito strani. Preden izvedemo katerokoli poizvedbo z mysqli, najprej preverimo, ali je uporabnik prijavljen.

<?php
session_start();

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

// Če uporabnik ni prijavljen, ga preusmerimo
if (!isset($_SESSION['user_id'], $_SESSION['logged_in'])) {
    header('Location: 15_login.php');
    exit();
}

// Šele nato odpremo povezavo z bazo
$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() . ')'
    );
}

// Zaščitena vsebina strani
echo 'Dobrodošli na zaščiteni strani.';

mysqli_close($connection);
?>

Pri tem pristopu preverimo prijavo pred povezavo z bazo. To je smiselno, ker neprijavljenemu uporabniku ni treba izvajati nadaljnje kode strani.

Osnovni primer s PDO

Enako zaščito lahko uporabimo tudi pri delu z PDO. Seja je pri zaščiti strani pomembnejša od tega, kateri vmesnik uporabljamo za povezavo s podatkovno zbirko.

<?php
session_start();

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

// Če uporabnik ni prijavljen, ga preusmerimo
if (!isset($_SESSION['user_id'], $_SESSION['logged_in'])) {
    header('Location: 15_login.php');
    exit();
}

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

    echo 'Dobrodošli na zaščiteni strani.';
}
catch (PDOException $e) {
    echo 'Zaščitene strani trenutno ni mogoče prikazati.';
}
?>

Pri PDO je zaščita strani enaka kot pri drugih pristopih: najprej preverimo sejo, nato izvedemo kodo zaščitene strani.

Dodatna zaščita dejanj na strani

Sama zaščita strani ni vedno dovolj. Če stran vsebuje obrazce za dodajanje, urejanje ali brisanje podatkov, moramo zaščititi tudi posamezna dejanja.

  • obrazce zaščitimo s CSRF žetonom,
  • brisanje in podobna dejanja dovolimo samo prek metode POST,
  • vhodne podatke dodatno validiramo,
  • za delo z bazo uporabljamo pripravljene poizvedbe,
  • po izvedenem dejanju uporabnika preusmerimo nazaj na administrativno stran.

Pri brisanju zapisov je na primer smiselno preveriti metodo zahtevka:

if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
    header('Location: 16_admin.php');
    exit();
}

Pozor: Dejanja, ki spreminjajo podatke, naj ne bodo izvedena z navadno povezavo. Za brisanje, urejanje in dodajanje uporabljamo obrazce in metodo POST.

CSRF zaščita

CSRF zaščita preprečuje, da bi bil obrazec oddan brez uporabnikove namerne potrditve iz druge strani. Uporabimo jo pri obrazcih, ki spreminjajo podatke.

Najprej ustvarimo žeton in ga shranimo v sejo:

if (empty($_SESSION['csrf_token'])) {
    $_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}

Nato ga dodamo v obrazec kot skrito polje:

<input type="hidden" name="csrf_token" value="<?php echo htmlspecialchars($_SESSION['csrf_token']); ?>">

Ob oddaji obrazca preverimo, ali je žeton pravilen:

if (
    !isset($_POST['csrf_token'], $_SESSION['csrf_token']) ||
    $_POST['csrf_token'] !== $_SESSION['csrf_token']
) {
    die('Neveljaven obrazec.');
}

Pomni: CSRF zaščita ne preverja pravilnosti podatkov. Preveri samo, ali je obrazec prišel iz pričakovanega postopka. Podatke obrazca moramo še vedno posebej validirati.

Primerjava zaščite strani in zaščite dejanj

Zaščita strani in zaščita dejanj sta povezani, vendar ne pomenita iste stvari.

Zaščita strani Zaščita dejanj
Preveri, ali je uporabnik prijavljen. Preveri, ali je oddano dejanje veljavno.
Uporablja $_SESSION['user_id'] in podobne podatke. Uporablja metodo POST, CSRF žeton in validacijo podatkov.
Neprijavljenega uporabnika preusmeri na prijavo. Neveljaven zahtevek zavrne ali preusmeri nazaj.
Izvede se na začetku zaščitene datoteke. Izvede se pred obdelavo obrazca ali pred spremembo podatkov.

Aplikacija Knjige

📘Aplikacija Knjige

V priloženih datotekah administrativnega dela aplikacije je zaščita strani izvedena enotno. Na začetku strani se zažene seja in preveri, ali sta v seji nastavljena podatka user_id in logged_in. Če uporabnik ni prijavljen, se takoj preusmeri na prijavno stran.

Takšno zaščito uporabljajo obrazci za dodajanje knjige, dodajanje z izpisom zadnjega ID-ja, večkratni vnos ter obrazec za urejanje zapisa. Na vrhu zaščitenih strani se nato izpiše tudi prijavljeni uporabnik in povezava za odjavo.

Posebej pomembno je tudi varno brisanje, kjer je dejanje dovoljeno samo prek metode POST, zahtevek pa mora imeti veljaven csrf_token. Če zaščita ni veljavna, se uporabnik vrne na administrativno stran z opozorilom.

Primer: zaščitena stran – 16_vnos-obrazec.php
Primer: zaščitena stran – 16_lastID-obrazec.php
Primer: zaščitena stran – 16_multivnos-obrazec.php
Primer: zaščitena stran – 16_uredi-obrazec.php
Primer: zaščiteno dejanje – 16_brisi.php
  1. Na začetku strani zaženemo sejo s session_start().
  2. Preverimo, ali v seji obstajajo podatki o prijavi uporabnika.
  3. Če uporabnik ni prijavljen, ga preusmerimo na prijavno stran.
  4. Po preusmeritvi uporabimo exit().
  5. Šele nato prikažemo zaščiteno vsebino ali odpremo povezavo z bazo.
  6. Za obrazce in dejanja uporabimo CSRF zaščito.
  7. Pri brisanju in podobnih dejanjih dovolimo samo metodo POST.
  8. Vse vhodne podatke preverimo na strežniku.
  9. Za delo z bazo uporabimo pripravljene poizvedbe.
  10. Na zaščitenih straneh dodamo tudi možnost odjave.

Priporočila

  • Zaščito prijave izvedi na začetku vsake zaščitene datoteke.
  • Ne zanašaj se samo na skrite povezave v navigaciji.
  • Po preusmeritvi vedno uporabi exit().
  • Obrazce za spremembo podatkov zaščiti s CSRF žetonom.
  • Za brisanje uporabi metodo POST.
  • Vse podatke iz obrazcev validiraj na strežniku.
  • Za poizvedbe, ki uporabljajo uporabniški vnos, uporabi pripravljene poizvedbe.
  • Pri izpisu podatkov uporabi htmlspecialchars().
  • Na zaščitenih straneh omogoči jasno povezavo za odjavo.

Pogoste napake

  • Zaščitena stran ne preverja podatkov v $_SESSION.
  • Preverjanje prijave je izvedeno šele po prikazu dela strani.
  • Po funkciji header() manjka exit().
  • Zaščita je izvedena samo v meniju, ne pa v sami datoteki.
  • Obrazci za spreminjanje podatkov nimajo CSRF zaščite.
  • Brisanje zapisov je omogočeno z metodo GET.
  • Podatki iz obrazca niso preverjeni na strežniku.
  • Za delo z bazo niso uporabljene pripravljene poizvedbe.
  • Zaščitene strani ne omogočajo jasne odjave.

Zaščita strani in zaščita posameznih dejanj morata delovati skupaj. Prijava uporabnika omogoči dostop do strani, CSRF zaščita, metoda POST in validacija podatkov pa varujejo posamezne spremembe podatkov.