Pěkný článek, jen si myslím, že Post-Redirect-Get je obecně rozšířený omyl, který dává pocit bezpečnosti, ale prakticky nic moc neřeší.
Hlavním jeho nedostatkem je, že neřeší situaci, kdy uživatel použije v prohlížeči zpět a formulář odešle znovu (což je častější případ než že zmáčkne F5). Takže je to jen polovičaté řešení, které vůbec nemusí řešit to, co si klade za cíl. Proto se taky skoro nepoužívá (tedy ne z důvodu ochrany proti opakovanému odeslání dat, jak je prezentováno v článku).
Tento problém se řeší až na straně aplikace. A to tak, že každý formulář obsahuje jedinečný klíč (jak je dnes dobrým zvykem i v rámci ochrany proti CSRF), podle kterého aplikace pozná, jestli se jedná o opakovaně odeslaný formulář.
Názory k článku
Post-Redirect-Get (nejen) v ASP.NET MVC
Post-Redirect-Get je kontroverzní návrhový vzor
celé vláknoRe: Post-Redirect-Get je kontroverzní návrhový vzor
celé vláknoPoužíváme Post-Redirect-Get ve všech aplikacích tak asi 10 let a nemáme jediný problém s duplicitou odeslaných dat. Neznám ani framework s podporou jedinečných klíčů. To je pro změnu můj názor za léta programování pro web.
Re: Post-Redirect-Get je kontroverzní návrhový vzor
celé vláknoDíky za reakci a doplnění. Použití tlačítka Zpět je opravdu jedním z dalších problémů, který PRG řeší (a IMHO ho opravdu řeší).
Upřímně řečeno mě ani nenapadl problém s tlačítkem zpět (tedy to, že by ho PRG neřešil). Když server vrátí jako reakci na POST jen hlavičku s redirektem (nějakou třístovku), tak prohlížeč tuto POSTovanou adresu do historie neuloží (teď testováno na IE8, Firefox 3 a Chrome 2) – však by ani nevěděl, jaký title jí přiřadit.
Samozřejmě jistě existují prohlížeče či konfigurace prohlížečů, které nám do historie uloží i POSTované stránky, ale IMHO jsou v drtivé menšině.
Re: Post-Redirect-Get je kontroverzní návrhový vzor
celé vláknoIMHO pán architekt měl na mysli situaci, kdy se uživatel po regulérním odeslání formuláře vrátí na předchozí stránku s formulářem a znovu klikne na „Odeslat“. To opravdu redirect after POST nevyřeší.
Což pravda nic nemění na tom, že redirect after POST je naprosto základní technika, která by se měla aplikovat vždy a všude :-)
Re: Post-Redirect-Get je kontroverzní návrhový vzor
celé vláknoJde o to, že po tom použití tlačítka zpět uživatel znovu odešle formulář. Typicky stránka (skript) která formulář zpracuje je v pořádku (všechny data se zpracovaly v pořádku) a až na té přesměrované se něco „pokazí“ (nenačte se správně, internal server error, atp.). Když dá uživatel F5, nic se nestane, protože to řeší PRG. Ale spousta (většina) uživatelů F5 nezná/nepoužívá, dají místo toho zpět a pošlou formulář znovu.
Mimochodem s tím jedinečným klíčem ve formuláři se dá pořešit i situace, kdy dojde k neočekávané chybě už během zpracovávání dat z formuláře. Né vždy může celé zpracování probíhat jako transakce, která když se nedokončí, tak se nic nestane. Po opětovném odeslání formuláře uživatelem může aplikace ověřit/dokončit předchozí pokus o zpracování dat.
Re: Post-Redirect-Get je kontroverzní návrhový vzor
celé vláknoAha, přečetl jsem si Vaši reakci ještě jednou a už asi chápu, co jste myslel – návrat na původní GETnutou stránku (často vytaženou z cache) a její další odeslání. Pak je jistě dobrou ochranou použití CSRF tokenu. V ASP.NET MVC to lze velmi jednoduše – při renderování View stačí zavolat Html.AntiForgeryToken() a POSTovací akci odekorovat atributem ValidateAntiForgeryToken.
Re: Post-Redirect-Get je kontroverzní návrhový vzor
celé vláknoJasně, ale je tu jeden zásadní rozdíl: při Post-Redirect-Get je jediná možnost, jak může uživatel formulář odeslat ta, že vyvolá explicitní odeslání formuláře (tj. např. siskne tlačítko ve formuláří, nebo odentruje pole ve formuláři). Nikdy se to nestane mimoděk (např. tlačítkem zpět, apod.).
Opakované odeslání klávesou F5 to neřeší
celé vláknoOpakované odeslání klávesou F5 to neřeší, pokud F5 stiskneme ještě v době, kde se zpracovává první stránka (tedy ta, která zpracovává přijmutá data a následně odesílá redirect).
Ale jinak samozřejmě je základní technika, i když ji nelze použít pro zabránění odeslání formuláře vícekrát.
Re: Opakované odeslání klávesou F5 to neřeší
celé vláknoDle mého názoru řeší (teď otestováno na IE8, Firefox 3 a Chrome 2). Browsery totiž po stisku F5, CTRL + F5 i refreshovacího tlačítka začnou znovu načítat poslední stránku z historie, kamžto se POSTovací request vůbec nedostane.
Návrhový vzor
celé vláknoDík za moc pěkný článek, jen bych PRG rozhodně nenazval návrhovým vzorem.
informace uzivateli o uspesnosti / neuspesnosti
celé vláknoZdravim,
PRG je nahodou celkem pekne reseni (asi otazka nazoru). Rad bych ale nadhodil k diskuzi, jak resit vetsnou uzivatelu ocekavanou zpravu o uspesnosti / neuspesnosti pozadavku.
situace rekneme pridani uzivatele v nejakem foru: GET /user/add
- uzivatel vyplni formular POST /user/add
- server zpracuje pozadavek a uzivatele do DB prida, posle 303 na /user/list GET /user/list
- uzivatel vidi seznam jiz existujicich uzivatelu
ovsem u tohoto seznamu urcite nechce hledat sveho uzivatele, zda byl pridan nebo ne. Tedy rad by videl krasnou zdlenou hlasku „uzivatel uspesne pridan“
jedno reseni, ktere me napada je poslat si zpravu jako get parametr, napriklad /user/list?msg=uspesne%20pridan
toto reseni ma ale tu nevyhodu, ze ted kdyz uzivatel provede reload, uvidi zpravu znovu a zdesi se, ze uzivatel byl pridan znovu. Jine reseni me napada pridat si treba hlavicku do HTTP, kterou pak parsuji napr. javascriptem a na zaklade ni uzivateli zobrazim zpravu. Ale to mi zase neprijde moc koser vuci HTTP
jak toto resite vy?
Re: informace uzivateli o uspesnosti / neuspesnosti
celé vlákno- 1) Kdykoliv během zpracování požadavku na POST /user/add se uloží do session informace o úspěšnosti přidání.
- 2) Provede se přesměrování na GET /user/list
- 3) Při zpracování požadavku na GET /user/list se kouknu do session, jestli tam není nějaká zpráva k zobrazení.
- 4) Na konci zpracování požadavku vymažu data se session.
Možná to vypadá složitě, ale princip fungování TempData je prostý:
- 1) Na začátku požadavku se načtou do kolekce TempData všechny položky ze session (resp. načtou se z nějaké položky session, která je kolekcí).
- 2) Na konci požadavku se do session uloží jen ty hodnoty z TempData, které byly přidány nebo upraveny.
Předání dat při přesměrování pak vypadá tak, že před přesměrováním data do TempData uložím a ve zpracování požadavku na GET /user/list se prostě kouknu do TempData, jestli tam něco není.
Btw. v ASP.NET MVC může být TempData implementováno více způsoby – defaultně je to pomocí session, ale existuje i varianta s cookies.
Re: informace uzivateli o uspesnosti / neuspesnosti
celé vláknoVida, do session si ukladat veci, o kterych pri nejblizsi prilezitosti informovat … proc me to nenapadlo driv :)
Dobry napad, dik
Re: informace uzivateli o uspesnosti / neuspesnosti
celé vláknoA jak řešíte situace kdy není jednoznačné kam chce dále uživatel pokračovat, například nechce zobrazit seznam, ale přímo záznam vytvořeného uživatele, nebo chce ten seznam, nebo dokonce zůstat na stránce a přidat dalšího uživatele, atd…? Na straně serveru při zpracování post vyberete ten správný redirect, pokud ano, dává Vám to smysl? ;)
Re: informace uzivateli o uspesnosti / neuspesnosti
celé vláknoNo tak to už samozřejmě záleží jen na návrhu GUI, co přesně se má stát po úspěšném přidání uživatele ;-)
Jinak v jednom projektu to mám udělané tak, že do TempData ukládám záznam s klíčem „message“ a pokud je v TempData již uložená, kopíruji ji do ViewData. Master page šablona pak vypadá tak, že je připravena na zobrazení zprávy z ViewData (pokud je tam něco uložené). Tak mám jednoduše udělané, že každá stránka je schopna zobrazit jakoukoliv zprávu.
Re: Post-Redirect-Get (nejen) v ASP.NET MVC
celé vláknoRedirekty a různé sofistikované ochrany před XSRF jsou pěkná věc, ale jen do chvíle, než opravdu potřebujete nějakou akci udělat 10× a místo 10-ti zmáčknutí F5 musíte 10× vyplňovat tatáž data. Proto mám raději po vyplnění formuláře stránku, na které je jen a pouze výsledek operace a bez redirektu (automatického).
Re: Post-Redirect-Get (nejen) v ASP.NET MVC
celé vláknoAbych pravdu řekl, nikdy jsem tohle (jako uživatel) nepotřeboval.
Můj názor je takový, že když jste to někde potřeboval tak jsou (minimálně) dvě možnosti:
1) Dělal to tam každý uživatel a web byl tudíž blbě navržen, protože měl s tímto scénařem počítat.
2) Dělal jste to tam "jen vy", tedy byl to raritní případ, a pro ty IMHO nemá smysl optimalizovat.