Skip to content

Latest commit

 

History

History

08-uzivatele-maily

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 
 
 
 
 
 
 
 
 

8. Uživatelé, maily

Opakování z předchozích cvičení

👉

Ve cvičení 06 jsme se zabývali prací s databází a uložením dat v session a cookies. Tyto znalosti poté byly využity v ukázkovém příkladu ve cvičení 07, ve kterém jsme začali s přihlašováním uživatelů.

👉

Ohledně session byste si měli pamatovat:

  • session slouží k uložení dat, která chceme uchovat na serveru mezi jednotlivými požadavky uživatele (např. přihlášení či položky v košíku)
  • pro spuštění session je nutné zavolat funkci session_start()
  • následně máte session data k dispozici v globálním poli $_SESSION
  • do session jde uložit libovolná serializovatelná data (řetězce, čísla, pole, serializovatelné objekty)
  • pro identifikaci konkrétního uživatele se používá kód, který je u uživatel uložen v cookie

👉

Ohledně uživatelských účtů jsme zatím nastínili, že:

  • pro přihlašování přímo na daném webu používáme obvykle kombinaci jména či mailu a hesla
  • heslo nikdy neukládáme do databáze v čitelné podobě!
  • k hashování hesel jsme použili funkci password_hash a pro ověření hesla při přihlášení pak funkci password_verify
  • informaci o přihlášeném uživateli ukládáme v session

Kontroly formulářů

👉

Na předchozích cvičeních jsme se zabývali také kontrolami formulářů. S ohledem na opakující se připomínky a následné dotazy k těmto kontrolám si připomeňme, že:

  • tam, kde je to možné, je vhodné do formuláře zapsat kontroly v HTML 5 (a případně v javascriptu),
  • bez ohledu na kontroly uvedené v předchozím bodu je nutné mít kontroly i na straně PHP (a kontrolovat je nutné i např. položky vybírané ze selectu),
  • chybové hlášky musí uživateli konkrétně říct, co má opravit,
  • ve formuláři musí zůstat vyplněná aspoň data, která byla správně (aby je uživatel nemusel vyplňovat znovu).

👉

Část z vás si stále ještě není jistá, jak ověřit funkčnost kontroly v PHP, když chybu odchytí už prohlížeč. Máme na výběr 2 varianty:

  • pracnější varianta: z formuláře ty kontroly v HTML 5 a javascriptu dočasně odstraníme;
  • rozumnější varianta: dočasně danou stránku upravíte ve formuláři:
    1. kliknete pravým tlačítkem myši na vybrané pole a zvolíte prozkoumat/inspect
    2. kontrolu hodnot atp. odstraníme v rámci vývojářské konzole (jde tak např. dopsat i chybnou hodnotu do selectu atp.).

👉

Na tomto cvičení nás čeká:


Uživatelské účty

👉

Příklad s uživatelskými účty jsme trochu nakousli už u aplikace Nástěnka, ale je nutné, abychom to probrali podrobněji.

Nejprve si projdeme trochu nezbytné teorie a poté se podíváme na praktický příklad.

Autentizace vs. autorizace

👉

V souvislosti s uživatelskými účty a oprávněními uživatelů se velmi často setkáváme s termíny autentizace a autorizace. Oba se vztahují k tomu, jestli může daný uživatel s naší aplikací provádět nějaké operace, ale každý znamená trošku něco jiného.

👉

Autentizace

  • jde o identifikaci uživatele (např. jeho přihlášení)
  • autentizace (z angličtiny) = autentifikace (asi z francouzštiny :) = authentization = kdo jsem = zjištění totožnosti uživatele
  • analogie s řidičským průkazem: Kdo je řidič? Jméno, příjmení, fotka. Pokud nás zastaví, zda jsme to my.
  • uživatele můžeme identifikovat řadou různých způsobů - viz dále

👉

Autorizace

  • jde o ověření, zda může uživatel provést v naší aplikaci nějakou operaci (např. upravit danou stránku)
  • analogie s řidičským průkazem: Když nás zastaví na Harley, máme na řidičáku skupinu A?
  • nejčastěji řešíme oprávnění formou uživatelských rolí (jednou či několika pro každého uživatele)
    • např. administrátor může v e-shopu upravovat zboží, přihlášený uživatel si ho může koupit, nepřihlášený jen prohlížet
  • oprávnění uživatelů by měla vyplývat z analýzy případů použití (use-case model)
  • podrobněji k autorizaci

Metody autentizace uživatelů

👉

Existuje celá řada variant, jak ověřit, jestli je daný uživatel tím, za koho se chce vydávat.

Z jednotlivých metod bychom si měli vybrat podle toho, jak moc kritická data naše aplikace obsahuje. Jde o jakýsi kompromis mezi bezpečností a tím, jak moc chceme uživatele prudit.)

👉

Běžné jednoduché autentizace:

  • HTTP autentizace
  • lokální přihlašování ověřené podle údajů v databázi
  • lokální ověření proti autentizačnímu serveru (LDAP, Active Directory od Microsoftu, ...)
  • přihlašování pomocí externí služby
    • např. pomocí Google účtu, účtu na Facebooku atp.
    • v současnosti jde nejčastěji o přihlášení protokolem OAuth, což si ukážeme ve cvičení 11
    • patří sem také OpenId servery (např. mojeId), přihlašování pomocí Shibbolethu (používané např. v sítích univerzit) atp.
    • uživatele to zbavuje nutnosti pamatovat si další přihlašovací údaje a nás např. nutnosti implementovat dvoufaktorovou autentizaci
  • ideální stav je takový, kdy si uživatel může vybrat mezi lokálním přihlášením a přihlášením pomocí externí služby

👉

Vícefaktorová autentizace:

  • jde o ověření nejen znalosti hesla, ale obvykle také toho, zda uživatel vlastní nějaké zařízení
  • patří sem např. přihlašování pomocí certifikátů, zasílání SMS atp.
  • populární variantou je zabezpečení přihlašování pomocí autentifikátorů
    • nejčastěji aplikace v mobilu, např. Google Authenticator, Microsoft Authenticator atd.
    • uživatel musí kromě jména a hesla zadat také kód, který se mění cca 1x za minutu
    • pro PHP můžeme využít existující knihovny - např. sonata-project/google-authenticator

Hashování hesel

👉

  • Heslo nikdy neuchováváme v databázi ani v kódu aplikace v čitelné podobě!
    • Je zde vždy riziko, že se nám např. k datům v databázi někdo dostane - a v případě nešifrovaných hesel by je pak útočník jednoduše získal.
    • Většina uživatelů nemá pro každou aplikaci (službu) unikátní heslo, ale má jich jen několik, která střídají (v řadě případů dokonce mají např. jen 1 heslo pro banku a jedno jiné pro všechny ostatní služby) => když by útočník zjistil dané heslo z naší aplikace, může ho rozhodně zkusit použít i pro další služby.
  • Místo čitelného hesla ukládáme jeho hash
    • = jednosměrný otisk dat získaný pomocí známé matematické funkce
    • z hashe nejde přímo zjistit původní heslo, ale dá se zjistit jiný řetězec, který má stejný hash
    • jelikož nejde z hashe nejde získat původní heslo, nemůže nám ho aplikace při obnově zapomenutého hesla poslat -> může nám nabídnout jen možnost jeho změny
  • Příklady hashovacích funkcí - MD5, SHA1, SHA256, komplet seznam viz funkce hash.
    - pokud chcete bezpečnou funkci, použijte SHA256 nebo SHA512, případně BCRYPT - i starší funkce se ale dají použít, pokud v aplikaci hesla vylepšujeme za pomocí "soli" (salt)

👉

Solení hesel

  • jde o způsob, jak i z jednoduchého hesla udělat složitější
  • salt (sůl) = náhodná data, která jsou přimíchána do výsledného hashe (nebo uložena bokem) z původních dat
  • smyslem je zamezit útokům pomocí tzv. rainbow table (duhová tabulka) - tzv. reverzní hashing = předvypočtené seznamy výsledků hashovacích funkcí, ze kterých lze odvodit původní vstupní data = ideální pro zjištění hesla, pokud se útočník nějak dostane k hashům
  • Co si pod tím představit v praxi?
    • Uživatel nám zadal heslo "heslo" -> přidáme do něj nějaký další (ideálně náhodný) řetězec, tzv. sůl - výsledkem může být např. "he78D/4slo" -> tento řetězec zahashujeme a výsledek uložíme, včetně přimíchaného řetězec "78D/4" (ten můžeme např. připojit k hashi)
    • Při přihlášení uživatele provedeme stejnou operaci s jediným rozdílem - sůl negenerujeme náhodně, ale získáme ji z místa, kam jsme si ji uložili. A výsledky následně porovnáme.
  • nemusíme to dělat ručně, výchozí funkce pro práci s hesly to celé umí udělat i automaticky
  • pokud hesla solíme, můžeme použít i jinak ne zrovna bezpečnou hashovací funkci (např. wordpress také používá funkci md5 s přimícháním soli)

HTTP autentizace

👉

  • = metoda autentizace, která je definována přímo v protokolu HTTP
  • Jak to funguje?
    1. aplikace pošle http hlavičky vyžadující autentizaci
    2. prohlížeč zobrazí uživateli univerzální okno pro zadání uživatelského jména a hesla
    3. jméno a heslo zadané uživatelem pak prohlížeč zasílá v každém následujícím požadavku na server (tj. nejen požadavky na PHP skript, ale také na všechny obrázky atp.)
  • heslo se na server posílá nešifrované => pro bezpečné použití musíme být na https!
  • z pohledu uživatele má tato autentizace jednu podstatnou nevýhodu - nedá se z ní jednoduše odhlásit (to lze jen zavřením prohlížeče)
  • HTTP autentizace má více forem (Basic, Digest) - obvykle používáme Basic

👉

  • Tato metoda funguje dokonce i mimo vlastní aplikaci (ověření nám pak zajistí např. Apache) => s výhodou lze tuto metodu použít k dočasnému zabezpečení vyvíjené aplikace před tím, než ji budeme chtít spustit veřejně :)
    • pro využití mimo aplikaci stačí v dané složce umístit soubory .htaccess a .htpasswd

📘

Lokální přihlašování uživatelů

👉

  • obvykle využíváme kombinaci uživatelského jména či e-mailu a hesla
    • kombinace jména a hesla je o trošku bezpečnější (jde o další údaj, který musí uživatel znát), ale e-mail je z pohledu uživatele pohodlnější
    • při přihlašování pomocí mailu je uživatelsky přívětivější ignorovat velikost písmen
  • u hesla je vhodné vyžadovat alespoň jeho minimální délku, ale neměli bychom to s požadavky přehánět
    • popravdě řečeno např. požadavky na velké a malé písmeno, speciální znak, číslo a alespoň 10 znaků vedou jen k tomu, že si uživatel heslo někam uloží či napíše - rozhodně si ho nebude chtít pamatovat
    • požadavky by měly být přiměřené důležitosti naší aplikace a citlivosti v ní uložených dat
  • pokud nenutíme uživatele ověřit při registraci svůj e-mail, tak jej rovnou přihlásíme
    • aby nemusel zbytečně znovu zadávat své přihlašovací údaje, které zadal chvíli před tím při registraci

👉

Jak lokální přihlášení realizovat?

V databázi máme tabulku s uživateli, ve které máme kromě loginu či e-mailu také sloupec pro hash hesla (doporučeně varchar o délce max. 255 znaků)

👉

Registrace uživatele:

  • na zadání hesla se zeptáme 2x (abychom odchytili případné překlepy)
  • heslo zahashujeme funkcí password_hash a uložíme do databáze
$login = $_POST['login'];
$passwordHash = password_hash($_POST['password'],PASSWORD_DEFAULT);

//uložení uživatele do DB
$query = $db->prepare('INSERT INTO users (login, password) VALUES (:login, :password)');
$query->execute([
  ':login'=>$login,
  ':password'=>$passwordHash
]);

👉

Přihlášení uživatele:

  • podle zadaného přihlašovacího jména či e-mailu vybereme uživatele z databáze
  • ověříme platnost zadaného hesla pomocí password_verify
  • pokud nám ověření jména či hesla selže, zobrazíme uživateli jen obecnou hlášku o chybě (je to mezi formuláři jediná výjimka, kdy nechceme zobrazovat konkrétní chybu)
$login = $_POST['login'];
$password = $_POST['password'];

//načteme uživatele z DB
$query = $db->prepare('SELECT * FROM users WHERE login=:login LIMIT 1;');
$query->execute([
  ':login'=>$login,
]);             

if ($user=$query->fetch(PDO::FETCH_ASSOC)){
  if (password_verify($password, $user['password'])){
    //uložíme údaje uživatele do session
    $_SESSION['id']=$user['id'];
    $_SESSION['login']=$user['login'];
    //úspěšně přihlášeného uživatele přesměrujeme na cílovou stránku
    header('index.php');
  }
}

📘

📘

Příklady přihlašování v ukázkových aplikacích:

Oprávnění uživatelů

👉

Z hlediska oprávnění uživatelů (tj. jejich autorizace) potřebujeme vždy ověřit, jestli uživatel může provést danou operaci.

  • Uživateli zobrazujeme v aplikaci jen odkazy a formuláře, které má právo použít (tj. např. v e-shopu běžný uživatel nevidí odkaz na úpravu ceny zboží :)).
  • Ověřování provádíme ve všech skriptech, které mají být daným způsobem omezeny.
    • nemusí jít nutně o pokus o hack naší aplikace, ale uživatel se mohl např. odhlásit, ale na další záložce v prohlížeči mu zůstala zobrazená administrace naší aplikace

Možnosti ověření oprávnění uživatelů

👉

  • Nejjednodušší variantou je ověření, zda uživatel je či není přihlášen.
  • U nepatrně složitějších aplikací obvykle máme odlišeny administrátory a běžné uživatele - stačí na to 1 boolean hodnota uložená u daného uživatele v DB.
  • Ve složitějších aplikacích obvykle používáme uživatelské role.

Jak pracovat s uživatelskými rolemi?

👉

  • jednodušší variantou mít v aplikaci jednu sadu vzájemně se rozšiřujících rolí
    • např. v CMS máme role guest -> autor -> editor -> admin
    • uživatel pak má obvykle jen 1 roli, kterou u něj máme uloženou v DB ve sloupci v tabulce s uživateli
  • složitější variantou je možnost mít více rolí pro každého uživatele
    • uživatel by měl mít práva za všechny příslušné role najednou - nenuťte ho role přepínat!

Oprávnění k jednotlivým zdrojům

👉

Pokud máme rozsáhlejší či objektově psanou aplikaci a nechceme všude vypisovat role, které mají oprávnění provádět danou operaci, je vhodnější mít v aplikaci uložený seznam oprávnění, které se vztahují k jednotlivým rolím.

V praxi to může vypadat tak, že evidujeme identifikátor zdroje a jednotlivé operace. Například:

  • v aplikaci máme zdroj good
  • pro daný zdroj definujeme, jaké operace může provádět která role:
    • admin může provést všechny operace
    • seller má oprávnění k akcím show, create a update
    • guest má oprávnění pouze pro akci show
  • ověření role pak vypadá tak, že ověříme, jestli aktuální uživatel má např. oprávnění good-delete (což dle uvedeného výčtu mohou jen uživatelé s rolí admin

👉

POZOR: Pokud si píšete ověřování oprávnění sami, doporučuji mít oprávnění definovaná jen kladně (tj. výčet všech operací, které může uživatel provést).

  • pokud má uživatel více rolí, tak nám stačí, že oprávnění pro danou operaci má libovolná z jeho rolí.

📘

Příklad na ověřování oprávnění uživatelů pomocí zdrojů a rolí si ukážeme za týden.

Ukázková aplikace s uživatelskými účty

👉

Pro ukázku použití uživatelských účtů a možnosti rozlišení uživatelských rolí se podívejme na další verzi aplikace jednoduchého e-shopu, která v tomto případě disponuje možnostmi autentizace a autorizace uživatelů.

  • aplikaci může používat jen přihlášený uživatel
    • nepřihlášený uživatel je automaticky přesměrován na přihlašovací stránku signin.php
    • ověření je v souboru user_required.php
    • údaje o přihlášeném uživateli uchováváme v session
  • jen admin může měnit nabídku zboží
    • pro přihlašování administrátorů je využívána HTTP autentifikace
  • aplikace nemá ošetřené vstupy (prázdné heslo atp), pouze zamezuje SQL inject útoku - DIY :)

Zkuste si tuto aplikaci spustit a projděte si okomentované zdrojové kódy.

📘

  • postup zprovoznění ukázkové aplikace:
    1. stáhněte si celou složku aplikace (08-app-eshop) a nahrajte ji na server
    2. nahrajte do MariaDB strukturu databáze (pozor, schéma není stejné jako u předchozí verze e-shopu)
    3. nahrajte do MariaDB ukázková data
    4. nastavte vlastní xname a heslo k databázi v souboru db.php
  • část pro nepřihlášeného uživatele/databázová autentizace:
    • signup.php - registrace nového uživatele, ukázka práce s funkcí password_hash
    • signin.php - přihlášení existujícího uživatele, ukázka práce s funkcí password_verify
  • část pro autorizaci a autentizaci:
    • user required.php - soubor pro require, vynucení přihlášení uživatele, autentizace uložená v SESSION
    • admin required.php - soubor pro require, vynucení přihlášení administrátora, ukázka HTTP autentizace
  • část pro přihlášeného uživatele:
    • index.php - výpis zboží v e-shopu
    • buy.php - přidání zboží do košíku podle jeho ID
    • cart.php - výpis zboží přidaného do košíku
    • remove.php - smazání zboží z košíku
    • signout.php - odhlášení, zruší session
  • část pro administátora:
    • new.php - přidání nového zboží do e-shopu, začne se nabízet ke koupi
    • delete.php - smazání zboží z e-shopu, přestane se nabízet ke koupi
    • update.php - úprava zboží v e-shopu

👉

Výzva k zamyšlení:

  • Zvládli byste předělat aplikaci tak, aby se i administrátoři přihlašovali normálně a ne pomocí HTTP autentifikace?

Posílání e-mailů

👉

K čemu je dobré posílání mailů z PHP?

S posíláním mailů z PHP se setkáme v celé řadě aplikací. Jako příklady můžeme jmenovat:

  • v návaznosti na uživatelské účty např. pro poslání odkazu pro potvrzení platnosti e-mailové adresy či pro změnu hesla,
  • potvrzení objednávky z e-shopu,
  • zasílání novinek na webu pro odběratele,
  • upozornění administrátora na chybu v aplikaci.

👉

Co bychom naopak rozhodně dělat neměli?

  • Neměli bychom posílat spam - tj. např. reklamy a novinky uživatelům, kteří si je výslovně nevyžádali.
  • Rozhodně bychom neměli posílat maily, ve kterých se vydáváme za někoho jiného!

Jak e-mail odeslat?

👉

  • Přímo v PHP najdeme funkci mail(), která umí e-mail odeslat prostřednictvím unixového nástroje sendmail - tj. funguje na většině serverů.
  • Funkce mail() je ale poměrně hloupá - respektive řeší jen odeslání, ale ne sestavení e-mailu.
    • Hodí se ale např. pro jednoduché posílání notifikací administrátorům.
    • Upozornění: Na serveru eso.vse.cz funguje posílání e-mailů jen na školní adresy.
  • Pro složitější e-maily a posílání mailů např. přes jiný SMTP server obvykle použíme odpovídající knihovny.
    • jako univerzální knihovnu doporučuji PHPMailer
      • jednoduchá, srozumitelná knihovna umožňující poslat např. HTML mail s přílohami nejen sendmailem, ale i přes SMTP server
      • je použita také v dalších řešeních, např. ve WordPressu
      • instalace nejjednodušeji pomocí composeru
    • ve většině PHP frameworků jejich vlastní řešení pro posílání e-mailů, přičemž v některých případech jej můžeme použít i mimo framework

Poslání mailu funkcí mail:

mail($to, $subject, $message, $headers);//hlavičky jsou volitelné, ale je nutné do nich zadat např. info o odesílateli...

📘

Příklad a podklady:

📘

Řešení pro posílání mailů ve frameworcích:

Posílání velkého množtví e-mailů

👉

Pokud budete chtít z webu např. rozesílat newsletter či jinou formu reklam většímu množství uživatelů, či jen máte na serveru velký provoz např. v e-shopu, je vhodnější místo výchozího SMTP serveru použít nějaké řešení v podobě SaaS.

  • na managed hostingu vám pak nevypnou základní posílání mailů
  • nebudete muset tak moc řešit, zda nejste na spamovém blacklistu, škálování, balancování apt.
  • Pozor, většina normálních e-mailových schránek (např. gmail) má limit na počet odeslaných zpráv - tj. nemůžete je používat pro rozesílání velkého množství mailů, i když se k nim zvládnete přihlásit přes SMTP.

Příklady SMTP serverů jako SaaS:

  • Amazon SES - SMTP jako SaaS, pod Amazon Web Services (levný, spolehlivý)
  • Sendgrid - další SMTP server jako SaaS, velké objemy (i miliony mailů měsíčně; drahý, ale spolehlivý)
  • MailChimp - kompletní odesílání mailů jako SaaS (tvorba šablon, WYSIWYG editor, plánovač odesílání, tracking doručení i přečtení mailu příjemcem, garantuje doručení, velmi drahý)

👉

Otázka k zamyšlení: Jak lze poznat, že uživatel dostal do schránky mail, nebo si ho dokonce přečetl?

Domácí úkol

🏠

Domácí úkol vychází z ukázkové aplikace Nástěnka s uživatelskými účty.

Nezbytná příprava:

  1. pokud jste ji zatím neviděli, prohlédněte si prezentaci s postupem implementace přihlašování uživatelů 📙
  2. stáhněte si zdrojové kódy
  3. nahrajte zdrojový kód aplikace na server eso.vse.cz
  4. naimportujte SQL export do databáze

Vaším úkolem je:

  • doplnit do aplikace rozlišení rolí uživatelů - budeme rozlišovat běžné uživatele a administrátory (0,5 bodu)
    • stačí jeden vhodný sloupec v DB, při registraci je uživatel automaticky v roli běžného uživatele
    • žádnou stránku pro administraci uživatelů dělat nemusíte
  • administrátoři mohou upravovat a mazat všechny příspěvky, běžní uživatelé jen příspěvky vlastní (1 bod)
  • pro administrátory doplňte možnost přidávat, upravovat a odebírat kategorie, ve kterých jsou příspěvky zařazeny (2 body)
    • při smazání kategorie můžete smazat všechny do ní zařazené příspěvky, neřešte jejich převod do jiné kategorie
    • ideálně to bude nějaká samostatná stránka, na kterou budou mít přístup jen administrátoři (a také na ni jen oni uvidí odkaz z hlavní stránky aplikace)

Způsob a termín odevzdání:

Vytvořenou aplikaci nahrajte na server eso.vse.cz a odkaz na ni vložte do příslušného zadání v MS Teams.