Zamenjava gesla uporabniku omogoča, da si po pozabljenem ali potečenem geslu nastavi novo varno geslo. Pri tem moramo najprej preveriti, ali je povezava za ponastavitev veljavna, nato pa novo geslo varno shraniti v bazo.
Postopek zamenjave gesla je varnostno občutljiv, ker uporabniku omogoča dostop do računa brez starega gesla. Zato moramo preveriti identifikator uporabnika, identifikator zahtevka, žeton, čas veljavnosti povezave in pravilnost novega gesla.
Pomni: Novo geslo pred shranjevanjem vedno pretvorimo v hash s funkcijo password_hash(). Gesla ne shranjujemo v navadnem besedilu.
Reset zahtevek naj bo časovno omejen. V prikazanem zgledu je povezava veljavna 30 minut, nato pa je ne smemo več uporabiti za zamenjavo gesla.
Vsebina strani
Osnovna pravila
Zamenjava gesla je običajno sestavljena iz dveh ločenih delov. Najprej preverimo povezavo za ponastavitev gesla, nato pa uporabniku omogočimo vnos novega gesla.
- Iz povezave preberemo identifikator uporabnika, identifikator zahtevka in žeton.
- Vrednosti iz povezave preverimo kot nezaupan uporabniški vnos.
- V tabeli
password_reset_requestpoiščemo ustrezen reset zahtevek. - Preverimo, ali zahtevek še ni potekel.
- Veljavne podatke začasno shranimo v
$_SESSION. - Uporabniku prikažemo obrazec za novo geslo.
- Novo geslo preverimo in ga pretvorimo v hash.
- Geslo posodobimo v tabeli
users. - Uporabljeni reset zahtevek izbrišemo.
Pozor: Reset žeton je občutljiv podatek. Ne shranjujemo ga v javne izpise in ga po uspešni uporabi odstranimo oziroma izbrišemo skupaj z zahtevkom.
Zamenjava gesla uporabnika
Zamenjava gesla je običajno del postopka za obnovitev dostopa do uporabniškega računa. Uporabnik po e-pošti prejme posebno povezavo, ki vsebuje identifikacijske podatke zahtevka za reset.
Postopek navadno poteka v dveh korakih:
- preverimo, ali je povezava za reset veljavna,
- uporabniku prikažemo obrazec za vnos novega gesla.
Po uspešni nastavitvi novega gesla staro geslo ne velja več, novo geslo pa moramo pred shranjevanjem pretvoriti v varen hash.
Pri posodobitvi gesla uporabimo ukaz:
UPDATE users
SET password = :password
WHERE id = :id;
Po uspešni zamenjavi gesla uporabljeni reset zahtevek odstranimo:
DELETE FROM password_reset_request
WHERE id = :id;
Pomni: Reset zahtevek izbrišemo zato, da iste povezave ni mogoče uporabiti večkrat.
Osnovni primer z mysqli
Spodnji zgled prikazuje osnovni postopek zamenjave gesla z uporabo mysqli: preverjanje reset zahtevka, preverjanje novega gesla in posodobitev uporabnika v tabeli users.
<?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() . ')'
);
}
// 1. korak: preverjanje povezave za reset
$userId = $_GET['uid'] ?? '';
$token = $_GET['t'] ?? '';
$requestId = $_GET['id'] ?? '';
if (
filter_var($userId, FILTER_VALIDATE_INT) !== false &&
filter_var($requestId, FILTER_VALIDATE_INT) !== false &&
trim((string)$token) !== ''
) {
$stmt = mysqli_prepare(
$connection,
"SELECT id, user_id, date_requested, token
FROM password_reset_request
WHERE user_id = ? AND token = ? AND id = ?"
);
mysqli_stmt_bind_param($stmt, 'isi', $userId, $token, $requestId);
mysqli_stmt_execute($stmt);
$result = mysqli_stmt_get_result($stmt);
$requestInfo = mysqli_fetch_assoc($result);
if ($requestInfo) {
$requestedAt = strtotime($requestInfo['date_requested']);
if ($requestedAt !== false && (time() - $requestedAt) <= 1800) {
$_SESSION['reset_pwd_user_id'] = (int)$requestInfo['user_id'];
$_SESSION['reset_pwd_request_id'] = (int)$requestInfo['id'];
$_SESSION['reset_pwd_token'] = $token;
}
}
mysqli_stmt_close($stmt);
}
// 2. korak: nastavitev novega gesla
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$password = $_POST['pwd'] ?? '';
$rePassword = $_POST['re-pwd'] ?? '';
if (
$password !== '' &&
$rePassword !== '' &&
$password === $rePassword &&
mb_strlen($password) >= 8 &&
isset($_SESSION['reset_pwd_user_id'])
) {
$passwordHash = password_hash($password, PASSWORD_DEFAULT);
$stmt = mysqli_prepare(
$connection,
"UPDATE users SET password = ? WHERE id = ?"
);
mysqli_stmt_bind_param($stmt, 'si', $passwordHash, $_SESSION['reset_pwd_user_id']);
mysqli_stmt_execute($stmt);
mysqli_stmt_close($stmt);
$stmt = mysqli_prepare(
$connection,
"DELETE FROM password_reset_request WHERE id = ?"
);
mysqli_stmt_bind_param($stmt, 'i', $_SESSION['reset_pwd_request_id']);
mysqli_stmt_execute($stmt);
mysqli_stmt_close($stmt);
unset(
$_SESSION['reset_pwd_user_id'],
$_SESSION['reset_pwd_request_id'],
$_SESSION['reset_pwd_token']
);
$_SESSION['password-reset'] = 'Vaše geslo je bilo uspešno spremenjeno.';
header('Location: 15_login.php');
exit();
}
}
mysqli_close($connection);
?>
Pri mysqli uporabimo pripravljene stavke za iskanje reset zahtevka, posodobitev gesla in brisanje uporabljenega zahtevka.
Osnovni primer s PDO
Tudi z vmesnikom PDO zamenjavo gesla izvedemo z varnim preverjanjem reset zahtevka, validacijo novega gesla in posodobitvijo hashiranega gesla v tabeli uporabnikov.
<?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);
// 1. korak: preverjanje reset povezave
$userId = $_GET['uid'] ?? '';
$token = $_GET['t'] ?? '';
$requestId = $_GET['id'] ?? '';
if (
filter_var($userId, FILTER_VALIDATE_INT) !== false &&
filter_var($requestId, FILTER_VALIDATE_INT) !== false &&
trim((string)$token) !== ''
) {
$stmt = $pdo->prepare(
"SELECT id, user_id, date_requested, token
FROM password_reset_request
WHERE user_id = :user_id
AND token = :token
AND id = :id"
);
$stmt->execute([
':user_id' => (int)$userId,
':token' => $token,
':id' => (int)$requestId
]);
$requestInfo = $stmt->fetch(PDO::FETCH_ASSOC);
if ($requestInfo) {
$requestedAt = strtotime($requestInfo['date_requested']);
if ($requestedAt !== false && (time() - $requestedAt) <= 1800) {
$_SESSION['reset_pwd_user_id'] = (int)$requestInfo['user_id'];
$_SESSION['reset_pwd_request_id'] = (int)$requestInfo['id'];
$_SESSION['reset_pwd_token'] = $token;
}
}
}
// 2. korak: nastavitev novega gesla
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_SESSION['reset_pwd_user_id'])) {
$password = $_POST['pwd'] ?? '';
$rePassword = $_POST['re-pwd'] ?? '';
if ($password !== '' && $rePassword !== '' && $password === $rePassword && mb_strlen($password) >= 8) {
$stmt = $pdo->prepare(
"SELECT id, user_id, date_requested, token
FROM password_reset_request
WHERE id = :id
AND user_id = :user_id
AND token = :token"
);
$stmt->execute([
':id' => (int)$_SESSION['reset_pwd_request_id'],
':user_id' => (int)$_SESSION['reset_pwd_user_id'],
':token' => $_SESSION['reset_pwd_token']
]);
$requestInfo = $stmt->fetch(PDO::FETCH_ASSOC);
if ($requestInfo) {
$requestedAt = strtotime($requestInfo['date_requested']);
if ($requestedAt !== false && (time() - $requestedAt) <= 1800) {
$passwordHash = password_hash($password, PASSWORD_DEFAULT);
$stmt = $pdo->prepare("UPDATE users SET password = :password WHERE id = :id");
$stmt->bindValue(':id', (int)$_SESSION['reset_pwd_user_id'], PDO::PARAM_INT);
$stmt->bindValue(':password', $passwordHash, PDO::PARAM_STR);
$stmt->execute();
$stmt = $pdo->prepare("DELETE FROM password_reset_request WHERE id = :id");
$stmt->bindValue(':id', (int)$_SESSION['reset_pwd_request_id'], PDO::PARAM_INT);
$stmt->execute();
unset(
$_SESSION['reset_pwd_user_id'],
$_SESSION['reset_pwd_request_id'],
$_SESSION['reset_pwd_token']
);
$_SESSION['password-reset'] = 'Vaše geslo je bilo uspešno spremenjeno.';
header('Location: 15_login.php');
exit();
}
}
}
}
}
catch (PDOException $e) {
echo 'Zamenjava gesla trenutno ni mogoča.';
}
?>
Pri PDO uporabimo poimenovane parametre. Pred posodobitvijo gesla reset zahtevek znova preverimo, ker uporabnik obrazec za novo geslo lahko odpre pozneje ali ga poskusi oddati po poteku povezave.
Preverjanje reset povezave
Preden uporabniku dovolimo nastavitev novega gesla, moramo preveriti, ali je povezava za zamenjavo gesla veljavna.
| Podatek | Preverjanje |
|---|---|
uid |
Identifikator uporabnika mora biti veljavno celo število. |
id |
Identifikator reset zahtevka mora biti veljavno celo število. |
t |
Žeton ne sme biti prazen in se mora ujemati z zapisom v tabeli password_reset_request. |
date_requested |
Datum zahtevka ne sme biti starejši od dovoljenega časa veljavnosti. |
Čas veljavnosti lahko preverimo tako:
$requestedAt = strtotime($requestInfo['date_requested']);
if ($requestedAt !== false && (time() - $requestedAt) <= 1800) {
// zahtevek je še veljaven
}
Pozor: Reset zahtevek preverimo ob odpiranju povezave in ponovno ob oddaji obrazca. Tako preprečimo uporabo potekle povezave, ki je bila odprta še pred potekom.
Preverjanje in shranjevanje novega gesla
Pred shranjevanjem novega gesla preverimo, ali sta obe polji izpolnjeni, ali se gesli ujemata in ali je novo geslo dovolj dolgo.
- novo geslo ne sme biti prazno,
- ponovljeno geslo ne sme biti prazno,
- obe gesli se morata ujemati,
- geslo mora imeti vsaj določeno minimalno dolžino,
- pred shranjevanjem uporabimo
password_hash().
if ($password === $rePassword && mb_strlen($password) >= 8) {
$passwordHash = password_hash($password, PASSWORD_DEFAULT);
}
Hashirano geslo nato shranimo v tabelo users:
UPDATE users
SET password = :password
WHERE id = :id;
Pomni: Po uspešni zamenjavi gesla moramo uporabniku omogočiti prijavo z novim geslom, uporabljeni reset zahtevek pa izbrisati.
Primerjava pristopov mysqli in PDO
Oba pristopa omogočata varno zamenjavo gesla s pripravljenimi stavki. Razlika je predvsem v zapisu parametrov in načinu branja rezultatov.
| Korak | mysqli |
PDO |
|---|---|---|
| Preverjanje reset zahtevka | mysqli_prepare() in mysqli_stmt_bind_param() |
$pdo->prepare() in poimenovani parametri |
| Branje zahtevka | mysqli_fetch_assoc() |
$stmt->fetch() |
| Hashiranje gesla | password_hash() |
password_hash() |
| Posodobitev gesla | UPDATE users SET password = ? WHERE id = ? |
UPDATE users SET password = :password WHERE id = :id |
| Brisanje zahtevka | DELETE FROM password_reset_request WHERE id = ? |
DELETE FROM password_reset_request WHERE id = :id |
Aplikacija Knjige
📘Aplikacija Knjige
V priloženi aplikaciji datoteka 15_zamenjavaPWD.php najprej prebere parametre uid, t in id, preveri njihovo osnovno veljavnost in nato v tabeli password_reset_request poišče ustrezen reset zahtevek. Če zahtevek obstaja in ni starejši od 30 minut, podatke shrani v sejo in uporabnika preusmeri na obrazec za novo geslo.
Datoteka 15_zamenjavaPWD-obrazec.php nato prikaže obrazec za novo geslo. Ob oddaji obrazca znova preveri reset zahtevek, veljavnost časa in ujemanje novega gesla. Nato novo geslo pretvori v varno zgoščeno vrednost s funkcijo password_hash(), posodobi uporabnika v tabeli users in izbriše uporabljeni reset zahtevek.
Po uspešni zamenjavi gesla aplikacija počisti podatke iz seje, nastavi sporočilo za prijavno stran in uporabnika preusmeri nazaj na prijavo. Če je povezava neveljavna ali potekla, se uporabniku prikaže ustrezno opozorilo.
15_zamenjavaPWD.php15_zamenjavaPWD-obrazec.phpNavodila za izdelavo zamenjave gesla
- Iz povezave preberemo identifikator uporabnika, identifikator zahtevka in žeton.
- Preverimo, ali so vsi podatki veljavni.
- V tabeli
password_reset_requestpoiščemo ustrezen reset zahtevek. - Preverimo, ali zahtevek še ni potekel.
- Če je zahtevek veljaven, podatke začasno shranimo v sejo.
- Uporabniku prikažemo obrazec za novo geslo.
- Ob oddaji obrazca reset zahtevek ponovno preverimo.
- Preverimo ujemanje in dolžino novega gesla.
- Novo geslo pretvorimo v varno zgoščeno vrednost s funkcijo password_hash() in posodobimo tabelo
users. - Po uspešni spremembi izbrišemo reset zahtevek, počistimo sejo in uporabnika preusmerimo na prijavo.
Pozor: Pri uspešni zamenjavi gesla je priporočljivo razveljaviti oziroma izbrisati uporabljeni reset zahtevek. Tako se povezava ne more ponovno uporabiti.
Priporočila
- Reset povezava naj bo časovno omejena.
- Žeton naj bo dovolj dolg in naključno ustvarjen.
- Podatke iz URL naslova vedno preveri.
- Reset zahtevek preveri ob odpiranju povezave in ob oddaji obrazca.
- Novo geslo shrani samo kot hash.
- Po uspešni zamenjavi izbriši uporabljeni reset zahtevek.
- Po zamenjavi gesla počisti začasne podatke iz seje.
- Uporabnika preusmeri na prijavno stran in mu prikaži splošno potrditveno sporočilo.
Pogoste napake
- Reset povezava nima časovne omejitve.
- Žeton se ne preveri v podatkovni bazi.
- Reset zahtevek se preveri samo ob odpiranju povezave, ne pa tudi ob oddaji obrazca.
- Novo geslo se shrani v navadnem besedilu.
- Po uspešni zamenjavi reset zahtevek ostane v tabeli.
- Začasni podatki za reset gesla ostanejo v
$_SESSION. - Pri posodobitvi gesla ni uporabljena pripravljena poizvedba.
- Po klicu
header()manjkaexit().
Zamenjava gesla mora preprečiti večkratno uporabo iste povezave. Zato moramo po uspešni spremembi gesla uporabljeni zahtevek odstraniti in počistiti začasne podatke iz seje.