Načrtovanje in razvoj spletnih aplikacij

Registracija uporabnika

Registracija uporabnika omogoča ustvarjanje novega računa v aplikaciji. Uporabnik vnese svoje podatke, aplikacija jih preveri, nato pa novega uporabnika shrani v podatkovno zbirko.

Registracija je varnostno občutljiv postopek, ker aplikacija obdeluje osebne podatke in geslo. Zato moramo podatke preveriti, geslo shraniti kot hash, preprečiti podvojena uporabniška imena in e-poštne naslove ter uporabniku prikazati jasna sporočila.

Pomni: Gesla pri registraciji nikoli ne shranimo v navadnem besedilu. Pred shranjevanjem uporabimo funkcijo password_hash(), pri prijavi pa geslo preverimo s funkcijo password_verify().

Pri registraciji moramo poleg oblike podatkov preveriti tudi, ali uporabniško ime ali e-poštni naslov že obstajata v tabeli users.

Vsebina strani

Osnovna pravila

Pri registraciji ustvarimo nov uporabniški račun. Ker podatki prihajajo iz obrazca, jih moramo obravnavati kot nezaupan vnos.

  • Obrazec za registracijo naj uporablja metodo POST.
  • Vsa obvezna polja morajo biti izpolnjena.
  • E-poštni naslov preverimo s FILTER_VALIDATE_EMAIL.
  • Geslo in ponovljeno geslo se morata ujemati.
  • Geslo mora biti dovolj dolgo in primerno zahtevno.
  • Uporabniško ime in e-poštni naslov morata biti enkratna.
  • Geslo pred shranjevanjem pretvorimo v hash.
  • Za delo z bazo uporabimo pripravljene stavke.
  • Po uspešni registraciji uporabnika preusmerimo na potrditveno ali prijavno stran.

Pozor: Preverjanje enkratnosti uporabniškega imena v aplikacijski kodi ni dovolj, če tabela nima ustreznih omejitev. V tabeli users je priporočljivo uporabiti tudi enolični indeks za username in mail.

Registracija novega uporabnika

Pri registraciji uporabnik običajno izpolni obrazec z osnovnimi podatki, kot so ime, priimek, uporabniško ime, e-poštni naslov in geslo. Aplikacija nato preveri pravilnost vnesenih podatkov in ustvari nov uporabniški račun.

Osnovna sintaksa za vnos novega uporabnika v tabelo je:

INSERT INTO users (ime, priimek, username, mail, password)
VALUES (vrednost1, vrednost2, vrednost3, vrednost4, vrednost5);

Pri registraciji gesla ne shranjujemo v navadnem besedilu. Pred shranjevanjem ga pretvorimo v varen hash, ki ga kasneje pri prijavi preverjamo s funkcijo password_verify().

Pred vnosom novega uporabnika pogosto izvedemo še poizvedbo, s katero preverimo, ali uporabniško ime ali e-poštni naslov že obstajata:

SELECT id
FROM users
WHERE username = :username OR mail = :mail;

Pomni: Pri registraciji običajno najprej preverimo podatke, nato preverimo podvajanje uporabnika, šele nato izvedemo INSERT INTO.

Osnovni primer z mysqli

Spodnji zgled prikaže osnovno registracijo uporabnika z uporabo mysqli, validacije polj in varnega shranjevanja gesla.

<?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() . ')'
    );
}

$error = '';

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $ime = trim($_POST['ime'] ?? '');
    $priimek = trim($_POST['priimek'] ?? '');
    $username = trim($_POST['uid'] ?? '');
    $email = trim($_POST['mail'] ?? '');
    $password = $_POST['pwd'] ?? '';
    $rePassword = $_POST['re-pwd'] ?? '';

    if ($ime === '' || $priimek === '' || $username === '' || $email === '' || $password === '' || $rePassword === '') {
        $error = 'Vsa polja morajo biti izpolnjena.';
    } elseif (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
        $error = 'E-poštni naslov ni veljaven.';
    } elseif ($password !== $rePassword) {
        $error = 'Gesli morata biti enaki.';
    } elseif (mb_strlen($password) < 8) {
        $error = 'Geslo mora imeti vsaj 8 znakov.';
    } else {
        $stmtCheck = mysqli_prepare(
            $connection,
            "SELECT id FROM users WHERE username = ? OR mail = ?"
        );
        mysqli_stmt_bind_param($stmtCheck, 'ss', $username, $email);
        mysqli_stmt_execute($stmtCheck);
        $resultCheck = mysqli_stmt_get_result($stmtCheck);

        if (mysqli_fetch_assoc($resultCheck)) {
            $error = 'Uporabniško ime ali e-pošta je že uporabljena.';
        } else {
            $passwordHash = password_hash($password, PASSWORD_DEFAULT);

            $stmtInsert = mysqli_prepare(
                $connection,
                "INSERT INTO users (ime, priimek, username, mail, password)
                 VALUES (?, ?, ?, ?, ?)"
            );
            mysqli_stmt_bind_param($stmtInsert, 'sssss', $ime, $priimek, $username, $email, $passwordHash);

            if (mysqli_stmt_execute($stmtInsert)) {
                $_SESSION['regIme'] = $ime;
                $_SESSION['regPriimek'] = $priimek;

                header('Location: 15_register_OK.php');
                exit();
            } else {
                $error = 'Registracija trenutno ni mogoča.';
            }

            mysqli_stmt_close($stmtInsert);
        }

        mysqli_stmt_close($stmtCheck);
    }
}

mysqli_close($connection);
?>

Pri mysqli sta uporabljeni dve pripravljeni poizvedbi. Prva preveri, ali uporabnik že obstaja, druga pa novega uporabnika shrani v tabelo users.

Osnovni primer s PDO

Tudi z vmesnikom PDO registracijo izvedemo s pripravljeno poizvedbo, preverjanjem enkratnosti uporabnika in shranjevanjem hashiranega gesla.

<?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);

    $error = '';

    if ($_SERVER['REQUEST_METHOD'] === 'POST') {
        $ime = trim($_POST['ime'] ?? '');
        $priimek = trim($_POST['priimek'] ?? '');
        $username = trim($_POST['uid'] ?? '');
        $email = trim($_POST['mail'] ?? '');
        $password = $_POST['pwd'] ?? '';
        $rePassword = $_POST['re-pwd'] ?? '';

        if ($ime === '' || $priimek === '' || $username === '' || $email === '' || $password === '' || $rePassword === '') {
            $error = 'Vsa polja morajo biti izpolnjena.';
        } elseif (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
            $error = 'E-poštni naslov ni veljaven.';
        } elseif ($password !== $rePassword) {
            $error = 'Gesli morata biti enaki.';
        } elseif (mb_strlen($password) < 8) {
            $error = 'Geslo mora imeti vsaj 8 znakov.';
        } else {
            $stmt = $pdo->prepare(
                "SELECT id FROM users WHERE username = :username OR mail = :mail"
            );
            $stmt->bindValue(':username', $username, PDO::PARAM_STR);
            $stmt->bindValue(':mail', $email, PDO::PARAM_STR);
            $stmt->execute();

            if ($stmt->fetch(PDO::FETCH_ASSOC)) {
                $error = 'Uporabniško ime ali e-pošta je že uporabljena.';
            } else {
                $passwordHash = password_hash($password, PASSWORD_DEFAULT);

                $query = $pdo->prepare(
                    "INSERT INTO users (ime, priimek, username, mail, password)
                     VALUES (:ime, :priimek, :username, :mail, :password)"
                );
                $query->execute(array(
                    ':ime' => $ime,
                    ':priimek' => $priimek,
                    ':username' => $username,
                    ':mail' => $email,
                    ':password' => $passwordHash
                ));

                $_SESSION['regIme'] = $ime;
                $_SESSION['regPriimek'] = $priimek;

                header('Location: 15_register_OK.php');
                exit();
            }
        }
    }
}
catch (PDOException $e) {
    $error = 'Registracija trenutno ni mogoča.';
}
?>

Pri PDO uporabimo poimenovane parametre, na primer :username, :mail in :password. Tak zapis je pregleden in primeren za obrazce z več polji.

Preverjanje podatkov pri registraciji

Pri registraciji moramo skrbno preveriti vse podatke, saj le tako preprečimo napačne vnose in podvojene uporabniške račune.

Podatek Preverjanje
ime Polje naj bo izpolnjeno in naj vsebuje dovoljene znake.
priimek Polje naj bo izpolnjeno in naj vsebuje dovoljene znake.
username Uporabniško ime naj bo izpolnjeno, ustrezne oblike in enkratno.
mail E-poštni naslov naj bo veljaven in enkraten.
pwd Geslo naj bo dovolj dolgo in naj ne bo prazno.
re-pwd Ponovljeno geslo se mora ujemati z geslom.

Pozor: Validacija v brskalniku ni dovolj. Tudi če obrazec uporablja HTML preverjanje, mora strežniška PHP koda ponovno preveriti vse prejete podatke.

Varno shranjevanje gesla

Geslo pred shranjevanjem pretvorimo v hash. Za to uporabimo funkcijo password_hash(). Pri sodobnem PHP je priporočljivo uporabiti PASSWORD_DEFAULT, saj PHP sam izbere primeren algoritem.

$passwordHash = password_hash($password, PASSWORD_DEFAULT);

Pri prijavi nato uporabimo funkcijo password_verify():

if (password_verify($passwordAttempt, $user['password'])) {
    // geslo je pravilno
}

Hash gesla shranimo v stolpec password v tabeli users.

Pomni: Hash ni enako kot šifriranje. Pri pravilnem shranjevanju gesel ne potrebujemo pridobiti izvirnega gesla nazaj, ampak samo preveriti, ali se vneseno geslo ujema s shranjenim hashom.

Potrditev uspešne registracije

Po uspešni registraciji uporabnika pogosto preusmerimo na potrditveno stran. Tam lahko prikažemo prijazno obvestilo in povezavo na prijavo.

$_SESSION['regIme'] = $ime;
$_SESSION['regPriimek'] = $priimek;

header('Location: 15_register_OK.php');
exit();

Podatkov, kot je geslo, nikoli ne shranjujemo v sejo za prikaz potrditve.

Pozor: Pred uporabo funkcije header() ne sme biti izpisa v brskalnik. Po preusmeritvi uporabimo exit(), da ustavimo nadaljnje izvajanje skripte.

Primerjava pristopov mysqli in PDO

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

Korak mysqli PDO
Preverjanje obstoječega uporabnika mysqli_prepare() in mysqli_stmt_bind_param() $pdo->prepare() in bindValue()
Vnos novega uporabnika INSERT INTO users s parametri ? INSERT INTO users s poimenovanimi parametri
Hashiranje gesla password_hash() password_hash()
Branje rezultata preverjanja mysqli_fetch_assoc() $stmt->fetch()
Preusmeritev header() in exit() header() in exit()

Aplikacija Knjige

📘Aplikacija Knjige

V priloženi aplikaciji je registracija izvedena v datoteki 15_register.php. Ob oddaji obrazca aplikacija prebere ime, priimek, uporabniško ime, e-pošto in obe gesli, nato pa preveri pravilnost vseh polj. Posebej preveri veljavnost e-pošte, dovoljene znake v imenu, priimku in uporabniškem imenu, enakost obeh gesel ter minimalno dolžino gesla.

Nato s pripravljeno poizvedbo PDO preveri, ali uporabniško ime oziroma ali e-poštni naslov že obstaja v tabeli users. Če uporabnik še ne obstaja, aplikacija geslo pretvori v varen hash z password_hash() in novega uporabnika shrani v bazo.

Po uspešni registraciji aplikacija v sejo shrani ime in priimek, nato pa uporabnika preusmeri na stran 15_register_OK.php, kjer se izpiše potrditveno sporočilo in povezava na prijavo.

Primer: aplikacija – 15_register.php
Primer: aplikacija – 15_register_OK.php
  1. Najprej pripravimo obrazec za vnos podatkov novega uporabnika.
  2. Ob oddaji obrazca preberemo vsa polja.
  3. Preverimo, ali so polja pravilno izpolnjena.
  4. Preverimo veljavnost e-poštnega naslova.
  5. Preverimo, ali se geslo in ponovljeno geslo ujemata.
  6. V bazi preverimo, ali uporabniško ime oziroma ali e-pošta že obstaja.
  7. Geslo pretvorimo v hash s funkcijo password_hash().
  8. Novega uporabnika shranimo v tabelo users.
  9. Po uspešni registraciji uporabnika preusmerimo na potrditveno ali prijavno stran.

Pozor: Če uporabniško ime ali e-pošta že obstajata, novega uporabnika ne vnesemo v tabelo. Najprej prikažemo sporočilo in uporabniku omogočimo popravek podatkov.

Priporočila

  • Vsa polja preveri na strežniku.
  • Za e-poštni naslov uporabi FILTER_VALIDATE_EMAIL.
  • Uporabniško ime in e-pošto preveri pred vnosom.
  • Geslo shrani samo kot rezultat funkcije password_hash().
  • Za vnos uporabnika uporabi pripravljen stavek.
  • Za username in mail uporabi enolične omejitve v tabeli users.
  • Po uspešni registraciji ne prikazuj gesla ali hasha.
  • Po preusmeritvi uporabi exit().

Pogoste napake

  • Geslo se shrani v navadnem besedilu.
  • Geslo se prikaže na potrditveni strani.
  • Obe gesli se ne primerjata pred vnosom.
  • E-poštni naslov ni preverjen.
  • Uporabniško ime ali e-pošta nista preverjena glede podvajanja.
  • Vnos uporabnika ni izveden s pripravljenim stavkom.
  • V tabeli users ni enoličnih omejitev za username in mail.
  • Po klicu header() manjka exit().

Registracija mora biti zasnovana tako, da varuje podatke uporabnika in podatkovno bazo. Najpomembnejši deli so preverjanje podatkov, pripravljene poizvedbe, enkratnost uporabnika in varno shranjevanje gesla.