Session ID-ի պաշտպանություն PHP-ում: PHP-ում նիստերի օգտագործման սխալները PHP նիստի միջոցով արժեքի կամ զանգվածի փոխանցում

Ողջույն, հարգելի համայնք։

Նախ ուզում եմ շնորհակալություն հայտնել շատ օգտակար ռեսուրսի համար: Մեկ անգամ չէ, որ այստեղ շատ հետաքրքիր գաղափարներ և գործնական խորհուրդներ եմ գտել:

Այս հոդվածի նպատակն է ընդգծել PHP-ում նիստերի օգտագործման սխալները: Իհարկե, կան PHP փաստաթղթեր և բազմաթիվ օրինակներ, և այս հոդվածը նախատեսված չէ լինել ամբողջական ուղեցույց: Այն նախագծված է բացահայտելու նիստերի հետ աշխատելու որոշ նրբերանգներ և պաշտպանելու ծրագրավորողներին ժամանակի ավելորդ վատնումից:

Սեսիաների օգտագործման ամենատարածված օրինակը, իհարկե, օգտագործողի թույլտվությունն է: Սկսենք ամենահիմնական իրականացումից, որպեսզի աստիճանաբար զարգացնենք այն, քանի որ նոր առաջադրանքներ են առաջանում:

(Տարածք և ժամանակ խնայելու համար մենք կսահմանափակենք մեր օրինակները միայն նստաշրջանի գործառույթներով՝ այստեղ լիարժեք թեստային հավելված կառուցելու գեղեցիկ դասի հիերարխիայով, սխալների համապարփակ մշակմամբ և այլ լավ բաներով):

Ֆունկցիան startSession() ( // Եթե նիստն արդեն սկսվել է, դադարեցրեք կատարումը և վերադարձրեք TRUE // (php.ini կարգավորումների ֆայլում session.auto_start պարամետրը պետք է անջատված լինի - լռելյայն արժեքը), եթե (session_id()) վերադարձնի true; else return session_start(); // Նշում. Մինչև 5.3.0 տարբերակը, session_start() ֆունկցիան վերադարձրեց TRUE, նույնիսկ եթե սխալ է տեղի ունեցել: // Եթե օգտագործում եք 5.3.0-ից առաջ տարբերակը, կատարեք լրացուցիչ ստուգում session_id() // session_start() ) ֆունկցիան կանչելուց հետո ոչնչացնում էSession() ( if (session_id()) ( // Եթե կա ակտիվ նիստ, ջնջեք նիստի թխուկները, setcookie(session_name(), session_id(), time( )-60*60*24); // և ոչնչացնել նիստը session_unset(); session_destroy();))

Նշում. Ենթադրվում է, որ ընթերցողն ունի հիմնական գիտելիքներ PHP նիստերի մասին, ուստի մենք այստեղ չենք լուսաբանի session_start() և session_destroy() ֆունկցիաների գործարկման սկզբունքը։ Մուտքի ձևի դասավորության և օգտատիրոջ նույնականացման առաջադրանքները կապված չեն հոդվածի թեմայի հետ, ուստի մենք դրանք նույնպես բաց կթողնենք: Հիշեցնեմ միայն, որ յուրաքանչյուր հաջորդ հարցում օգտատիրոջը նույնականացնելու համար, հաջող մուտք գործելու պահին, մենք պետք է օգտագործողի նույնացուցիչը պահենք սեսիայի փոփոխականում (օրինակ՝ userid անունով), որը հասանելի կլինի բոլոր հետագա հարցումներում: նիստի կյանքը: Անհրաժեշտ է նաև իրականացնել մեր startSession() ֆունկցիայի արդյունքի մշակումը։ Եթե ​​ֆունկցիան վերադարձնում է FALSE, ցուցադրեք մուտքի ձևը դիտարկիչում: Եթե ​​ֆունկցիան վերադարձրեց TRUE, և սեսիայի փոփոխական, որը պարունակում է լիազորված օգտատիրոջ նույնացուցիչը (մեր դեպքում՝ userid), ցուցադրեք լիազորված օգտատիրոջ էջը (սխալների հետ կապված լրացուցիչ տեղեկությունների համար տե՛ս 2013-06-ի լրացումը: 07 նիստի փոփոխականների բաժնում):

Առայժմ ամեն ինչ պարզ է. Հարցերը սկսվում են այն ժամանակ, երբ դուք պետք է կիրառեք օգտատերերի անգործության հսկողություն (սեսիայի ժամանակի ընդմիջում), մի քանի օգտվողների հնարավորություն ընձեռվի միաժամանակ աշխատել մեկ դիտարկիչում, ինչպես նաև պաշտպանել նիստերը չարտոնված օգտագործումից: Սա կքննարկվի ստորև:

Օգտագործողի անգործության վերահսկում ներկառուցված PHP գործիքների միջոցով Առաջին հարցը, որը հաճախ առաջանում է օգտատերերի համար տարբեր կոնսուլներ մշակողների մոտ, սեսիայի ավտոմատ դադարեցումն է օգտատիրոջ անգործության դեպքում: Չկա ավելի հեշտ բան, քան դա անելը՝ օգտագործելով PHP-ի ներկառուցված հնարավորությունները: (Այս տարբերակը առանձնապես հուսալի կամ ճկուն չէ, բայց մենք այն կդիտարկենք ամբողջականության համար):

Ֆունկցիան startSession() ( // Օգտատիրոջ անգործության ժամկետը (վայրկյաններով) $sessionLifetime = 300; if (session_id()) վերադարձել է true; // Սահմանել թխուկի կյանքի ժամկետը ini_set("session.cookie_lifetime", $sessionLifetime); // Եթե օգտվողը անգործության ժամկետը սահմանված է, սահմանեք սեսիայի ժամկետը սերվերում // Նշում. Արտադրական սերվերի համար խորհուրդ է տրվում նախադրել այս պարամետրերը php.ini ֆայլում, եթե ($sessionLifetime) ini_set("session.gc_maxlifetime", $sessionLifetime) ; if (session_start( )) (setcookie(session_name(), session_id(), time()+$sessionLifetime); վերադարձ true;) այլապես վերադարձել false;)

Մի քանի պարզաբանում. Ինչպես գիտեք, PHP-ն որոշում է, թե որ նիստը պետք է գործարկվի բրաուզերի կողմից ուղարկված քուքի անունով՝ հարցումի վերնագրում: Բրաուզերն իր հերթին ստանում է այս թխուկը սերվերից, որտեղ այն տեղադրում է session_start() ֆունկցիան։ Եթե ​​զննարկիչի քուքիի ժամկետը լրացել է, այն չի ուղարկվի հարցումում, ինչը նշանակում է, որ PHP-ն չի կարողանա որոշել, թե որ նիստը պետք է սկսել և դա կվերաբերվի որպես նոր նիստ ստեղծելու: PHP-ի կարգավորումների պարամետրը session.gc_maxlifetime, որը հավասար է մեր օգտվողի անգործության ժամանակին, սահմանում է PHP աշխատաշրջանի տևողությունը և վերահսկվում է սերվերի կողմից: Սեսիայի կյանքի տևողությունը վերահսկելն աշխատում է հետևյալ կերպ (այստեղ մենք համարում ենք ժամանակավոր ֆայլերում նիստերը պահելու օրինակ՝ որպես PHP-ի ամենատարածված և լռելյայն տարբերակ):

Երբ ստեղծվում է նոր նիստ, sess_ անունով ֆայլը ստեղծվում է գրացուցակում, որը սահմանված է որպես PHP կարգավորումների պարամետրի նիստերը պահելու գրացուցակ session.save_path, որտեղ է նստաշրջանի նույնացուցիչը: Հաջորդը, յուրաքանչյուր հարցումում, արդեն գոյություն ունեցող նիստը գործարկելու պահին, PHP-ն թարմացնում է այս ֆայլի փոփոխման ժամանակը: Այսպիսով, յուրաքանչյուր հաջորդ հարցումում PHP-ն, ըստ ընթացիկ ժամանակի և սեսիայի ֆայլի վերջին փոփոխության ժամանակի տարբերության, կարող է որոշել՝ արդյոք սեսիան ակտիվ է, թե՞ դրա կյանքի ժամկետն արդեն սպառվել է: (Հին նիստի ֆայլերը ջնջելու մեխանիզմը ավելի մանրամասն կքննարկվի հաջորդ բաժնում):

Նշում. Այստեղ պետք է նշել, որ session.gc_maxlifetime պարամետրը կիրառվում է մեկ սերվերի բոլոր սեսիաների համար (ավելի ճիշտ՝ մեկ հիմնական PHP գործընթացում): Գործնականում դա նշանակում է, որ եթե սերվերում աշխատում են մի քանի կայքեր, և դրանցից յուրաքանչյուրն ունի իր օգտագործողի անգործության ժամանակի վերջը, ապա կայքերից մեկում այս պարամետրը դնելը կհանգեցնի դրա կարգավորումը այլ կայքերի համար: Նույնը վերաբերում է համօգտագործվող հոսթինգին: Այս իրավիճակից խուսափելու համար նույն սերվերի յուրաքանչյուր կայքի համար օգտագործվում են առանձին նիստերի դիրեկտորիաներ: Նիստերի գրացուցակի ուղին սահմանելը կատարվում է php.ini կարգավորումների ֆայլում session.save_path պարամետրի միջոցով կամ ini_set() ֆունկցիան կանչելով: Դրանից հետո յուրաքանչյուր կայքի նիստերը կպահվեն առանձին գրացուցակներում, և կայքերից մեկում սահմանված session.gc_maxlifetime պարամետրը վավեր կլինի միայն դրա նիստի համար: Մենք մանրամասն չենք քննարկի այս դեպքը, մանավանդ, որ մենք ունենք օգտատերերի անգործության մոնիտորինգի ավելի ճկուն տարբերակ։

Օգտագործողի անգործության վերահսկում սեսիայի փոփոխականների միջոցով Թվում է, թե նախորդ տարբերակը, իր ողջ պարզությամբ (ընդամենը մի քանի լրացուցիչ տող կոդ), տալիս է այն ամենը, ինչ մեզ անհրաժեշտ է: Բայց ի՞նչ, եթե ոչ բոլոր հարցումները կարող են դիտվել որպես օգտագործողի գործունեության արդյունք: Օրինակ, էջն ունի ժմչփ, որը պարբերաբար AJAX հարցում է կատարում սերվերից թարմացումներ ստանալու համար: Նման հարցումը չի կարող դիտվել որպես օգտագործողի գործունեություն, ինչը նշանակում է, որ սեսիայի ժամկետի ավտոմատ երկարաձգումն այս դեպքում ճիշտ չէ: Բայց մենք գիտենք, որ PHP-ն ավտոմատ կերպով թարմացնում է նիստի ֆայլի փոփոխման ժամանակը ամեն անգամ, երբ կանչվում է session_start() ֆունկցիան, ինչը նշանակում է, որ ցանկացած հարցում կհանգեցնի սեսիայի ժամկետի երկարացմանը, և օգտագործողի անգործության ժամկետը երբեք տեղի չի ունենա: Բացի այդ, session.gc_maxlifetime պարամետրի բարդությունների մասին նախորդ բաժնի վերջին նշումը կարող է չափազանց շփոթեցնող և դժվար իրագործելի թվալ ոմանց համար:

Այս խնդիրը լուծելու համար մենք կհրաժարվենք ներկառուցված PHP մեխանիզմների օգտագործումից և կներկայացնենք մի քանի նոր սեսիա փոփոխականներ, որոնք թույլ կտան մեզ ինքնուրույն կառավարել օգտատերերի անգործության ժամանակը։

Գործառույթը startSession($isUserActivity=true) ($sessionLifetime = 300; if (session_id()) վերադարձել է true; // Սահմանել cookie-ի ժամկետը նախքան զննարկիչը փակելը (մենք կվերահսկենք ամեն ինչ սերվերի կողմից) ini_set("sesion. cookie_lifetime", 0) ; if (! session_start()) վերադարձնել false; $t = time(); if ($sessionLifetime) ( // Եթե օգտագործողի անգործության ժամկետը սահմանված է, // ստուգեք օգտվողի վերջին գործողությունից հետո անցած ժամանակը // (վերջին հարցման ժամանակը, երբ թարմացվել է վերջին ակտիվության սեսիայի փոփոխականը) if (isset($_SESSION["lastactivity"]) && $t-$_SESSION["lastactivity"] >= $sessionLifetime) ( // Եթե ժամանակն անցել է դրանից հետո օգտատիրոջ վերջին գործունեությունը, // ավելի մեծ է, քան անգործության ժամկետը, ինչը նշանակում է, որ նստաշրջանը սպառվել է, և դուք պետք է դադարեցնեք նստաշրջանը, deathSession(); վերադարձրեք false; եթե հարցումը ստացվել է օգտագործողի գործունեության արդյունքում, // թարմացրեք վերջին ակտիվության փոփոխականը ընթացիկ մեկ ժամանակի արժեքով, // դրանով իսկ երկարացնելով նիստի ժամանակը մեկ այլ sessionLifetime վայրկյանով, եթե ($isUserActivity) $_SESSION["lastactivity"] = $t; ) ) վերադարձնել ճշմարիտ; )

Եկեք ամփոփենք. Յուրաքանչյուր հարցումում մենք ստուգում ենք, թե արդյոք օգտատիրոջ վերջին գործողությունից մինչև ընթացիկ պահը լրացել է ժամկետը, և եթե այն հասել է, մենք ոչնչացնում ենք նիստը և ընդհատում գործառույթի կատարումը՝ վերադարձնելով FALSE: Եթե ​​ժամկետը չի հասել, և $isUserActivity պարամետրը TRUE արժեքով փոխանցվում է ֆունկցիային, մենք թարմացնում ենք օգտատիրոջ վերջին գործունեության ժամանակը: Մեզ մնում է միայն կանչող սկրիպտում որոշել, թե արդյոք հարցումը օգտատիրոջ գործունեության արդյունքն է, և եթե ոչ, կանչեք startSession ֆունկցիան՝ $isUserActivity պարամետրով, որը դրված է FALSE:

Լրացում 2013-06-07-ից ՍեսիանՍկսել() ֆունկցիայի արդյունքի մշակում

Մեկնաբանություններում նշվում էր, որ FALSE-ը վերադարձնելը չի ​​ապահովում սխալի պատճառի ամբողջական պատկերացում, և դա բացարձակապես արդարացի է: Ես այստեղ չեմ հրապարակել մանրամասն սխալների մշակում (հոդվածի երկարությունն արդեն բավականին մեծ է), քանի որ դա ուղղակիորեն կապված չէ հոդվածի թեմայի հետ: Բայց հաշվի առնելով մեկնաբանությունները, ես կպարզաբանեմ.

Ինչպես տեսնում եք, sessionStart ֆունկցիան կարող է երկու դեպքում վերադարձնել FALSE: Կամ նիստը չի կարող սկսվել որոշ ներքին սերվերի սխալների պատճառով (օրինակ՝ php.ini-ում սեսիայի սխալ կարգավորումներ), կամ սեսիայի ժամկետը սպառվել է: Առաջին դեպքում, մենք պետք է վերաուղղորդենք օգտատիրոջը մի էջ, որտեղ կա սխալ, որը նշում է, որ սերվերում խնդիրներ կան և աջակցության հետ կապվելու ձև: Երկրորդ դեպքում, մենք պետք է օգտագործողին տեղափոխենք մուտքի ձև և դրանում ցուցադրենք համապատասխան հաղորդագրություն, որում նշվում է, որ նիստի ժամկետը լրացել է: Դա անելու համար պետք է սխալի կոդեր մուտքագրել և FALSE-ի փոխարեն վերադարձնել համապատասխան կոդը, իսկ կանչելու եղանակով ստուգել այն և գործել համապատասխան։

Այժմ, նույնիսկ եթե սերվերի վրա նստաշրջանը դեռ գոյություն ունի, այն կկործանվի առաջին անգամ մուտք գործելիս, եթե օգտագործողի անգործության ժամկետը լրացել է: Եվ դա տեղի կունենա անկախ այն բանից, թե որ սեսիայի կյանքի տևողությունը սահմանված է գլոբալ PHP-ի կարգավորումներում:

Նշում. Ի՞նչ է պատահում, եթե զննարկիչը փակված է, և նստաշրջանի անվան քուքին ինքնաբերաբար ոչնչացվում է: Հարցումը սերվերին հաջորդ անգամ, երբ զննարկիչը բացվի, չի պարունակի նիստի թխուկը, և սերվերը չի կարողանա բացել նիստը և ստուգել օգտատիրոջ անգործության ժամկետը: Մեզ համար սա համարժեք է նոր նստաշրջան ստեղծելուն և որևէ կերպ չի ազդում ֆունկցիոնալության կամ անվտանգության վրա: Բայց արդարացի հարց է առաջանում՝ այդ դեպքում ո՞վ է քանդելու հին նիստը, եթե մինչ այժմ մենք այն ոչնչացրել ենք ժամանակի ավարտից հետո։ Թե՞ այն այժմ ընդմիշտ կախված կլինի նիստերի գրացուցակում: PHP-ում հին նիստերը մաքրելու համար կա մի մեխանիզմ, որը կոչվում է աղբահանություն: Այն աշխատում է սերվերին հաջորդ հարցման պահին և մաքրում է բոլոր հին նիստերը՝ հիմնվելով նիստի ֆայլերի վերջին փոփոխության ամսաթվի վրա: Բայց աղբահանության մեխանիզմը չի սկսվում սերվերին ուղղված յուրաքանչյուր հարցումից: Գործարկման հաճախականությունը (ավելի ճիշտ՝ հավանականությունը) որոշվում է պարամետրերի երկու պարամետրերով՝ session.gc_probability և session.gc_divisor: Առաջին պարամետրը երկրորդի վրա բաժանելու արդյունքը աղբահանության մեխանիզմի գործարկման հավանականությունն է։ Այսպիսով, որպեսզի նիստի մաքրման մեխանիզմը գործարկվի սերվերին ուղղված յուրաքանչյուր հարցումով, այդ պարամետրերը պետք է սահմանվեն հավասար արժեքներով, օրինակ՝ «1»: Այս մոտեցումը երաշխավորում է մաքուր նստաշրջանի գրացուցակը, բայց ակնհայտորեն չափազանց թանկ է սերվերի համար: Հետևաբար, արտադրական համակարգերում session.gc_divisor-ի լռելյայն արժեքը սահմանվում է 1000, ինչը նշանակում է, որ աղբահանության մեխանիզմը կաշխատի 1/1000 հավանականությամբ։ Եթե ​​փորձարկեք այս կարգավորումները ձեր php.ini ֆայլում, կարող եք նկատել, որ վերը նկարագրված դեպքում, երբ զննարկիչը փակում և մաքրում է իր բոլոր թխուկները, որոշ ժամանակով դեռևս հին նիստեր են մնացել նիստերի գրացուցակում: Բայց դա չպետք է ձեզ անհանգստացնի, քանի որ... ինչպես արդեն ասվել է, դա ոչ մի կերպ չի ազդում մեր մեխանիզմի անվտանգության վրա։

Թարմացում 2013-06-07-ից Սցեսի ֆայլերի արգելափակման պատճառով սկրիպտների սառեցման կանխարգելում

Մեկնաբանությունները բարձրացնում էին միաժամանակ գործարկվող սկրիպտների սառեցման հարցը՝ կապված նիստի ֆայլի արգելափակման հետ (ամենաուշագրավ տարբերակը երկար հարցումն է):

Սկզբից ես նշում եմ, որ այս խնդիրն ուղղակիորեն կախված չէ սերվերի բեռնվածությունից կամ օգտվողների քանակից: Իհարկե, որքան շատ են հարցումները, այնքան դանդաղ են կատարվում սցենարները: Բայց սա անուղղակի կախվածություն է։ Խնդիրն ի հայտ է գալիս միայն մեկ սեսիայի ընթացքում, երբ սերվերը ստանում է մի քանի հարցումներ մեկ օգտատիրոջ անունից (օրինակ՝ դրանցից մեկը երկար հարցում է, իսկ մնացածը՝ սովորական հարցումներ)։ Յուրաքանչյուր հարցում փորձում է մուտք գործել նույն նիստի ֆայլը, և եթե նախորդ հարցումը չի ապակողպել ֆայլը, ապա հաջորդ հարցումը կսպասի:

Նիստի ֆայլի կողպումը նվազագույնի հասցնելու համար խստորեն խորհուրդ է տրվում փակել նիստը` կանչելով session_write_close() ֆունկցիան անմիջապես նիստի փոփոխականների հետ բոլոր գործողությունների ավարտից հետո: Գործնականում դա նշանակում է, որ դուք չպետք է ամեն ինչ պահեք նիստի փոփոխականներում և մուտք գործեք դրանք սկրիպտի կատարման ողջ ընթացքում: Եվ եթե ձեզ անհրաժեշտ է որոշ աշխատանքային տվյալներ պահել սեսիայի փոփոխականներում, ապա անմիջապես կարդացեք դրանք, երբ սկսվում է նիստը, պահեք դրանք տեղական փոփոխականներում հետագա օգտագործման համար և փակեք նիստը (նշանակում է նիստը փակել՝ օգտագործելով session_write_close ֆունկցիան և ոչ թե ոչնչացնել այն՝ օգտագործելով session_destroy: )

Մեր օրինակում դա նշանակում է, որ նիստը բացելուց անմիջապես հետո, դրա ժամկետը և լիազորված օգտատիրոջ առկայությունը ստուգելուց հետո մենք պետք է կարդանք և պահպանենք հավելվածի կողմից պահանջվող բոլոր լրացուցիչ սեսիայի փոփոխականները (եթե այդպիսիք կան), այնուհետև փակենք նիստը զանգի միջոցով: դեպի session_write_close() և շարունակել սկրիպտի կատարումը, լինի դա երկար հարցում, թե սովորական հարցում:

Սեսիաների պաշտպանություն չարտոնված օգտագործումից Եկեք պատկերացնենք իրավիճակ. Ձեր օգտատերերից մեկը ստանում է տրոյան, որը խլում է բրաուզերի թխուկները (որում պահվում է մեր նիստը) և ուղարկում այն ​​նշված էլ. Հարձակվողը ստանում է թխուկը և օգտագործում այն ​​մեր լիազորված օգտատիրոջ անունից հարցումը խաբելու համար: Սերվերը հաջողությամբ ընդունում և մշակում է այս հարցումը, կարծես այն լիազորված օգտվողից է: Եթե ​​IP հասցեի լրացուցիչ ստուգումը չիրականացվի, ապա նման հարձակումը կհանգեցնի օգտատիրոջ օգտահաշվի հաջող կոտրմանը` դրանից բխող բոլոր հետևանքներով:

Ինչու՞ էր դա հնարավոր: Ակնհայտ է, որ քանի որ անունը և նստաշրջանի նույնացուցիչը միշտ նույնն են սեսիայի ողջ կյանքի ընթացքում, և եթե դուք ստանում եք այս տվյալները, կարող եք հեշտությամբ հարցումներ ուղարկել մեկ այլ օգտատիրոջ անունից (իհարկե, այս նստաշրջանի ողջ ընթացքում): Հնարավոր է, սա հարձակման ամենատարածված տեսակը չէ, բայց տեսականորեն այն բավականին իրագործելի է թվում, հատկապես հաշվի առնելով, որ նման տրոյան նույնիսկ կարիք չունի ադմինիստրատորի իրավունքների՝ կողոպտելու օգտատիրոջ բրաուզերի թխուկները:

Ինչպե՞ս կարող եք պաշտպանվել ձեզ նման հարձակումներից: Կրկին, ակնհայտորեն, սահմանափակելով նստաշրջանի նույնացուցիչի ժամկետը և պարբերաբար փոխելով նույնացուցիչը նույն նիստի ընթացքում: Մենք կարող ենք նաև փոխել նստաշրջանի անունը՝ ամբողջությամբ ջնջելով հինը և ստեղծելով նոր նիստ՝ դրա մեջ պատճենելով նիստի բոլոր փոփոխականները հինից։ Բայց դա չի ազդում մոտեցման էության վրա, ուստի պարզության համար մենք կսահմանափակվենք միայն նստաշրջանի նույնացուցիչով:

Հասկանալի է, որ որքան կարճ է նստաշրջանի ID-ի ժամկետը, այնքան հարձակվողը քիչ ժամանակ կունենա՝ ձեռք բերելու և օգտագործելու թխուկներ՝ օգտվողի հարցումը կեղծելու համար: Իդեալում, յուրաքանչյուր հարցման համար պետք է օգտագործվի նոր նույնացուցիչ, որը նվազագույնի կհասցնի ուրիշի նիստն օգտագործելու հնարավորությունը: Բայց մենք կքննարկենք ընդհանուր դեպքը, երբ նստաշրջանի նույնացուցիչի վերականգնման ժամանակը կամայականորեն սահմանվում է:

(Օրենսգրքի այն հատվածը, որն արդեն քննարկվել է, բաց կթողնենք):

Ֆունկցիան startSession($isUserActivity=true) ( ​​// Նիստի նույնացուցիչի կյանքի տևողությունը $idLifetime = 60; ... եթե ($idLifetime) ( // Եթե նստաշրջանի նույնականացման ժամկետը սահմանված է, // ստուգեք նիստի ավարտից հետո անցած ժամանակը ստեղծված կամ վերջին վերարտադրումը // (վերջին հարցման ժամանակը, երբ թարմացվել է նիստի փոփոխականի մեկնարկի ժամանակը) if (isset($_SESSION["starttime"])) ( if ($t-$_SESSION["starttime"] >= $ idLifetime) ( // Նիստի նույնացուցիչի ժամկետը լրացել է // Ստեղծել նոր նույնացուցիչ session_regenerate_id(true); $_SESSION["starttime"] = $t;) ) else ( // Մենք այստեղ ենք ստանում, եթե նիստը հենց նոր է ստեղծվել է // Նստաշրջանի նույնացուցիչի ստեղծման ժամանակը սահմանեք ընթացիկ ժամանակին $_SESSION["starttime"] = $t;) ) վերադարձ true;)

Այսպիսով, նոր նիստ ստեղծելիս (որը տեղի է ունենում, երբ օգտվողը հաջողությամբ մուտք է գործում), մենք սահմանում ենք նիստի փոփոխական մեկնարկի ժամանակը, որը մեզ համար պահում է նիստի վերջին սերնդի նույնացուցիչի ժամանակը, արժեքի, որը հավասար է ընթացիկ սերվերի ժամանակին: Հաջորդը, յուրաքանչյուր հարցումում մենք ստուգում ենք, թե արդյոք բավարար ժամանակ (idLifetime) անցել է նույնացուցիչի վերջին սերնդից, և եթե այո, մենք ստեղծում ենք նորը: Այսպիսով, եթե նույնացուցիչի սահմանված ժամկետի ընթացքում հարձակվողը, ով ստացել է լիազորված օգտատիրոջ թխուկը, ժամանակ չունենա օգտագործելու այն, ապա կեղծ հարցումը սերվերի կողմից կհամարվի որպես չարտոնված, և հարձակվողը կտեղափոխվի մուտքի էջ։ .

Նշում․ նոր սեսիայի ID-ն մտնում է բրաուզերի քուքի մեջ, երբ կանչվում է session_regenerate_id() ֆունկցիան, որն ուղարկում է նոր թխուկը, որը նման է session_start() ֆունկցիային, այնպես որ մենք ինքներս թխուկը թարմացնելու կարիք չունենք։

Եթե ​​մենք ցանկանում ենք հնարավորինս անվտանգ դարձնել մեր նիստերը, բավական է նույնացուցիչի ժամկետը սահմանել մեկ կամ նույնիսկ փակագծերից հանել session_regenerate_id() ֆունկցիան և հեռացնել բոլոր ստուգումները, ինչը կհանգեցնի յուրաքանչյուրի նույնացուցիչի վերականգնմանը: խնդրանք. (Ես չեմ փորձարկել այս մոտեցման ազդեցությունը կատարողականի վրա, և կարող եմ միայն ասել, որ session_regenerate_id(true) ֆունկցիան ըստ էության կատարում է ընդամենը 4 գործողություն՝ գեներացնել նոր նույնացուցիչ, ստեղծել վերնագիր նիստի քուքիով, ջնջել հինը և ստեղծել։ նոր նստաշրջանի ֆայլ):

Լիրիկական շեղում. Եթե պարզվում է, որ տրոյան այնքան խելացի է, որ չի ուղարկում թխուկներ հարձակվողին, այլ կազմակերպում է նախապես պատրաստված կեղծ հարցման ուղարկումը քուքի ստանալուց անմիջապես հետո, վերը նկարագրված մեթոդը, ամենայն հավանականությամբ, չի կարողանա պաշտպանեք նման հարձակումից, քանի որ տրոյականի կողմից թխուկը ստանալու և կեղծ հարցում ուղարկելու միջև գործնականում ոչ մի տարբերություն չի լինի, և մեծ հավանականություն կա, որ այս պահին նիստի նույնացուցիչը չի վերականգնվի:

Մի քանի օգտատերերի անունից մեկ բրաուզերում միաժամանակյա աշխատանքի հնարավորությունը Վերջին խնդիրը, որը ես կցանկանայի դիտարկել, մի քանի օգտվողների կողմից մեկ բրաուզերում միաժամանակյա աշխատանքի հնարավորությունն է: Այս հատկությունը հատկապես օգտակար է թեստավորման փուլում, երբ դուք պետք է ընդօրինակեք օգտատերերի միաժամանակյա աշխատանքը, և խորհուրդ է տրվում դա անել ձեր նախընտրած բրաուզերում, այլ ոչ թե օգտագործել ամբողջ հասանելի զինանոցը կամ բացել զննարկչի մի քանի օրինակներ ինկոգնիտո ռեժիմում: .

Մեր նախորդ օրինակներում մենք հստակորեն չենք նշել նստաշրջանի անունը, ուստի օգտագործվել է լռելյայն PHP անունը (PHPSESSID): Սա նշանակում է, որ մինչ այժմ մեր ստեղծած բոլոր նիստերը զննարկիչին ուղարկել են թխուկ՝ PHPSESSID անունով: Ակնհայտ է, որ եթե թխուկի անունը միշտ նույնն է, ապա նույն բրաուզերում նույն անունով երկու նիստ կազմակերպելու հնարավորություն չկա: Բայց եթե մենք օգտագործեինք մեր սեսիայի անունը յուրաքանչյուր օգտագործողի համար, խնդիրը կլուծվեր: Եկեք այդպես վարվենք։

Ֆունկցիան startSession($isUserActivity=true, $prefix=null) (... if (session_id()) վերադարձնում է true; // Եթե օգտագործողի նախածանցը փոխանցվում է պարամետրերում, // սահմանեք եզակի նստաշրջանի անուն, որը ներառում է սա նախածանց, // այլապես սահմանել ընդհանուր անուն բոլոր օգտագործողների համար (օրինակ՝ MYPROJECT) session_name("MYPROJECT".($prefix ? "_".$prefix: "")); ini_set("session.cookie_lifetime", 0); եթե (! session_start()) վերադարձնել կեղծ; ...)

Այժմ մնում է համոզվել, որ կանչող սկրիպտը յուրաքանչյուր օգտագործողի համար եզակի նախածանց է փոխանցում startSession() ֆունկցիային: Դա կարելի է անել, օրինակ, յուրաքանչյուր հարցման GET/POST պարամետրերում նախածանց փոխանցելով կամ լրացուցիչ թխուկի միջոցով:

Եզրակացություն Եզրափակելով, ես կներկայացնեմ PHP նիստերի հետ աշխատելու մեր գործառույթների ամբողջական վերջնական կոդը, ներառյալ վերը քննարկված բոլոր առաջադրանքները:

Գործառույթը startSession($isUserActivity=true, $prefix=null) ( $sessionLifetime = 300; $idLifetime = 60; if (session_id()) վերադարձել է true; session_name("MYPROJECT".($prefix ? "_".$prefix: "")); ini_set("session.cookie_lifetime", 0); if (! session_start()) վերադարձել է false; $t = time(); if ($sessionLifetime) (if (isset($_SESSION["lastactivity"] ) && $t-$_SESSION["lastactivity"] >= $sessionLifetime) (structSession(); return false; ) else ( if ($isUserActivity) $_SESSION["lastactivity"] = $t; ) ) եթե ($idLifetime ) ( if (isset($_SESSION["starttime"])) ( if ($t-$_SESSION["starttime"] >= $idLifetime) ( session_regenerate_id(true); $_SESSION["starttime"] = $t; ) ) else ($_SESSION["starttime"] = $t; ) ) վերադարձնել ճշմարիտ; ) ֆունկցիան ոչնչացնելSession() ( if (session_id()) ( session_unset(); setcookie(session_name(), session_id(), time() -60*60*24); session_destroy(); ) )

Հուսով եմ, որ այս հոդվածը որոշ ժամանակ կխնայի նրանց համար, ովքեր երբեք շատ չեն խորացել նիստերի մեխանիզմի մեջ, և բավականաչափ պատկերացում կտա այս մեխանիզմի մասին նրանց համար, ովքեր նոր են սկսում ծանոթանալ PHP-ին:

Ձեզ անհրաժեշտ է օգտանուն և գաղտնաբառ:

Հոդվածներ առցանց ներկայացնելու և ներկայացված հոդվածների կարգավիճակը ստուգելու համար դուք պետք է գրանցվեք և մուտք գործեք ձեր հաշիվ:

Հոդված ներկայացնելու համար պատրաստելու ստուգաթերթ

Որպես հոդվածի ներկայացման գործընթացի մաս, հեղինակները պետք է ստուգեն, որ իրենց հոդվածը համապատասխանում է բոլոր հետևյալ կետերին. հոդվածները կարող են վերադարձվել հեղինակներին, եթե նրանք չեն բավարարում այս պահանջներին:

Հոդվածը պատրաստվել է պահանջներին համապատասխան

Հեղինակային իրավունքի փոխանցման պայմանները

Հեղինակները պահպանում են ստեղծագործության հեղինակային իրավունքները և ամսագրին շնորհում են առաջին հրապարակման իրավունքներ ստեղծագործության հետ միասին՝ միաժամանակ արտոնագրելով այն Creative Commons Attribution License-ի պայմաններով, ինչը թույլ է տալիս ուրիշներին տարածել այս աշխատանքը՝ պարտադիր վերագրելով աշխատանքի հեղինակին և հղումով: այս ամսագրի բնօրինակ հրապարակմանը:

Գաղտնիության քաղաքականությունը

Այս ամսագրի կայքում մուտքագրված անուններն ու էլ. հասցեները կօգտագործվեն բացառապես այս ամսագրի կողմից սահմանված նպատակների համար և չեն օգտագործվի որևէ այլ նպատակի համար կամ տրվելու որևէ այլ անձի կամ կազմակերպության:

Նախքան համակարգում գրանցվելը օգտատերը համաձայնում է անձնական տվյալների մշակման և պահպանման քաղաքականությանը:

Հեղինակային վճարումներ

1500 նիշ բացատներով՝ 300,00 (RUB)

Ձեռագրի 1 էջի հրատարակում (1500 նիշ)՝ 300 ռուբլի։ Գրաֆիկական նյութերը/սեղանները վճարվում են առանձին՝ 50 ռուբլի / 1 հատ: Հեղինակի պատճենը, ներառյալ Ռուսաստանի տարածքում առաքումը, վճարվում է հեղինակի ցանկությամբ՝ 400 ռուբլի: Առաքում արտասահման - 800 ռուբլի: Հրապարակման համար նյութի ընդունման վկայական ուղարկելու արժեքը 150 ռուբլի է:

Ուղեկցող տեղեկատվության (լրիվ անվանումը, հեղինակների աշխատանքի վայրը, վերնագիրը, վերացական, հիմնաբառեր) թարգմանությունը անգլերեն 0,5 ռուբլի յուրաքանչյուր նիշի համար, ներառյալ բացատները:

Ուշադրություն. Հեղինակները (թեկնածուներ և գիտությունների դոկտորներ), ովքեր, ըստ elibrary.ru-ի, ունեն 300 և ավելի մեջբերում (ինքնամեջբերումների բաժինը պետք է լինի ոչ ավելի, քան 30%), հրապարակվում են անվճար։ Եթե ​​դուք իրավասու եք անվճար հրապարակման, նյութ ուղարկելիս, մեկնաբանությունների դաշտում նշեք ձեր գրադարանի պրոֆիլի հղումը՝ հղումների քանակով: Հավաքածուի առաքման ծախսերը վճարվում են առանձին:

Կայքի անվտանգությունը հիմնված է նիստերի կառավարման վրա: Երբ օգտատերը միանում է անվտանգ կայքին, նրանք տրամադրում են հավատարմագրեր՝ սովորաբար օգտագործողի անվան և գաղտնաբառի տեսքով: Վեբ սերվերը պատկերացում չունի, թե որ օգտվողն է արդեն մուտք գործել կամ ինչպես է նա նավարկում էջից էջ: Սեսիայի մեխանիզմը թույլ չի տալիս օգտվողներին գաղտնաբառ մուտքագրել ամեն անգամ, երբ նրանք ցանկանում են կատարել նոր գործողություն կամ գնալ նոր էջ:

Ըստ էության, նստաշրջանի կառավարումն ապահովում է, որ ներկայումս միացված օգտվողը նա է, ով վավերացվել է: Սակայն, ցավոք, նիստերը դարձել են ակնհայտ թիրախ հաքերների համար, քանի որ դրանք կարող են թույլ տալ մուտք գործել վեբ սերվեր՝ առանց նույնականացման անհրաժեշտության:

Օգտատիրոջ իսկությունը հաստատվելուց հետո վեբ սերվերը նրան տրամադրում է նիստի ID: Այս ID-ն պահվում է դիտարկիչում և փոխարինվում է, երբ նույնականացում է անհրաժեշտ: Սա թույլ է տալիս խուսափել մուտքի/գաղտնաբառի մուտքագրման կրկնվող գործընթացներից: Այս ամենը տեղի է ունենում հետին պլանում և անհարմարություն չի պատճառում օգտատիրոջը։ Պատկերացրեք, եթե ամեն անգամ նոր էջ դիտելիս մուտքագրեք ձեր օգտանունը և գաղտնաբառը:

Այս հոդվածում ես կփորձեմ ուրվագծել PHP-ում նիստի ID-ն պաշտպանելու բոլոր եղանակները, որոնք ես գիտեմ:

Թխուկների օգտագործումը Լռելյայնորեն, ամբողջ նիստի տեղեկատվությունը, ներառյալ ID-ն, ուղարկվում է թխուկի: Բայց դա միշտ չէ, որ տեղի է ունենում: Որոշ օգտվողներ անջատում են թխուկները իրենց բրաուզերում: Այս դեպքում զննարկիչը կփոխանցի նստաշրջանի ID-ն URL-ում:

Այստեղ ID-ն փոխանցվում է հստակ տեքստով, ի տարբերություն քուքի միջոցով նիստի, երբ տեղեկատվությունը թաքնված է HTTP վերնագրում: Սրանից պաշտպանվելու ամենապարզ միջոցը կլինի արգելել նիստի նույնացուցիչի փոխանցումը հասցեագոտի միջոցով: Դա կարելի է անել՝ գրելով հետևյալը Apache սերվերի .htaccess կազմաձևման ֆայլում.

Php_flag session.use_only_cookies միացված են

Գաղտնագրման օգտագործումը Եթե ձեր կայքը պետք է մշակի զգայուն տեղեկատվություն, ինչպիսիք են վարկային քարտերի համարները (բարև Sony-ից), դուք պետք է օգտագործեք SSL3.0 կամ TSL1.0 կոդավորումը: Դա անելու համար թխուկ դնելիս պետք է ապահով պարամետրի համար նշել true:

Եթե ​​նստաշրջանի գաղտնաբառը պահում եք $_SESSION փոփոխականում (դեռ ավելի լավ է օգտագործել sql), ապա չպետք է այն մաքուր տեքստով պահեք:

Եթե ​​($_SESSION["գաղտնաբառ"] == $userpass) ( // կոդը )

Վերոնշյալ կոդը անվտանգ չէ, քանի որ գաղտնաբառը պահվում է որպես պարզ տեքստ աշխատաշրջանի փոփոխականում: Փոխարենը, օգտագործեք md5 կոդավորումը, նման բան.

Եթե ​​($_SESSION["md5password"] == md5($userpass)) ( // կոդը)

Բրաուզերի ստուգում Մեկ այլ բրաուզերից (համակարգչից) սեսիա օգտագործելու հնարավորությունը կանխելու համար դուք պետք է մուտքագրեք օգտվողի գործակալի HTTP վերնագրի դաշտի ստուգում.

Session_start(); if (isset($_SESSION["HTTP_USER_AGENT"])) (եթե ($_SESSION["HTTP_USER_AGENT"] != md5($_SERVER["HTTP_USER_AGENT"])) ( // կոդը ) ) այլ ($_SESSION["HTTP_USER_AGENT"] ] = md5 ($_SERVER["HTTP_USER_AGENT"]);

Աշխատաշրջանի ժամկետի ավարտը Սահմանափակեք նիստի ժամկետը, ինչպես նաև թխուկների ժամկետը: Լռելյայնորեն, նստաշրջանի տևողությունը 1440 վայրկյան է: Դուք կարող եք փոխել այս արժեքը php.ini-ի և .htaccess-ի միջոցով: Օրինակ .htaccess-ի համար.

# Նիստի տևողությունը վայրկյաններով
php_value session.gc_maxlifetime 3600
# Cookie-ի կյանքի տևողությունը վայրկյաններով
php_value session.cookie_lifetime 3600

Կապում IP հասցեով Որոշ իրավիճակներում (ոչ միշտ), դուք պետք է կապեք IP հասցեով: Հիմնականում, երբ օգտատերերի թիվը սահմանափակ է և ունեն ստատիկ IP-ներ։ Ստուգումը կարող է հիմնված լինել կամ թույլատրված IP հասցեների ցանկի վրա,

Ներառել ("ip_list.php"); //$ip_white_list = զանգված ("admin1" => "111.222.333.444", "admin2" => "555.666.777.888"); if(!empty(array_search($_SERVER["REMOTE_ADDR"],$ip_white_list))) ( header ("Gation: admin.php"); ) else (echo "ACCESS DENY!";)

Կամ յուրաքանչյուր հարցման համար IP հասցեով (միայն ստատիկ IP-ի համար).

If(isset($_SESSION["ip"]) և $_SESSION["ip"] == $_SERVER["REMOTE_ADDR"]) ( header ("Տեղադրում. admin.php"); ) else ( session_unset (); $ _SESSION["ip"] = $_SERVER["REMOTE_ADDR"];)

Դուք պետք է տեղյակ լինեք, որ հաքերային հարձակումներից հնարավոր չէ ամբողջությամբ խուսափել: Դուք կարող եք միայն հնարավորինս դժվարացնել այս հաքը ցանկացած հայտնի միջոցներով: Այնուամենայնիվ, չպետք է մոռանալ նաև ձեր օրինական օգտատերերի մասին, որպեսզի չբարդացնեք նրանց կյանքը նման պաշտպանությամբ:

Այս հոդվածը գրվել է 2009 թվականին և մնում է մեր ամենահայտնի գրառումներից մեկը: Եթե ​​ցանկանում եք ավելին իմանալ PHP-ի և MySQL-ի մասին, կարող եք սա մեծ հետաքրքրություն առաջացնել:

ԾԱՆՈԹՈՒԹՅՈՒՆ. Այս հոդվածը թարմացվել է PHP 4.2 կամ ավելի նոր տարբերակով աշխատելու համար:

Վերջերս առիթ ունեցա մի խումբ մարդկանց հետ աշխատելու մի փոքրիկ նախագծի վրա։ Մենք վաղօրոք որոշել էինք, որ միայն այդ էլփոստը բավարար չէր լինելու բոլորին տեղեկության մեջ պահելու համար, ուստի ինձ հանձնարարվեց նախագծի համար փոքրիկ վեբ կայք կառուցել: Այն պետք է պարունակի պարզ հաղորդագրությունների տախտակ, մի վայր, որտեղ մենք կարող ենք փաստաթղթեր և այլ ֆայլեր վերբեռնել թիմի մնացած անդամների համար, և կոնտակտային տվյալներ թիմի տարբեր անդամների համար:

Որպեսզի այս գործառույթներից շատերը աշխատեն, ես գիտեի, որ ինձ պետք է, որ օգտվողները մուտք գործեն նախքան կայքի համապատասխան մասերը մուտք գործելը: Ինձ անհրաժեշտ էր մի համակարգ, որը թույլ կտա օգտատերերին գրանցվել օգտատերերի ID-ի համար՝ կայք մուտք գործելու համար, այնուհետև անմիջապես օգտագործել այդ ID-ն՝ առանց իմ կողմից որևէ միջամտության:

Այս հոդվածում ես կներկայացնեմ իմ մշակած համակարգի ակնարկը, սկսած առաջին կեսից օգտվողների գրանցման գործընթացից: Երկրորդ կեսում ես կկենտրոնանամ հենց կայքի վրա, թե ինչպես է այն պահանջում օգտվողներից մուտք գործել և այնուհետև պահպանել այդ մուտքի կարգավիճակը իրենց այցելության ընթացքում: Ես հատուկ ուշադրություն կդարձնեմ PHP-ում նիստերի կառավարման առանձնահատկությունների օգտագործմանը: Ի վերջո, դուք պետք է ունենաք բոլոր այն տեղեկությունները, որոնք անհրաժեշտ են ձեր սեփական նմանատիպ համակարգն իրականացնելու համար:

Այս հոդվածի ընթացքում ես ենթադրում եմ, որ դուք հիմնական ծանոթ եք PHP լեզվին, PHP սկրիպտին տեղեկատվություն ներկայացնելու ձևերի օգտագործմանը և ինչպես կարող է օգտագործվել PHP-ն MySQL տվյալների բազայի հետ փոխազդելու համար: Եթե ​​սրանցից որևէ մեկը ձեզ համար օտար հասկացություններ է, դուք պետք է սկսեք կարդալ իմ նախորդ հոդվածը, .

Մաս առաջին. Գրանցման գործընթացը Գրանցման ձևը

Կայքի կառուցումը սկսելու բնական վայր, որը օգտատերերից կպահանջի գրանցվել մուտքի համար, ինքնին գրանցման գործընթացն է: Ինչպես և կարելի էր ակնկալել, վեբ վրա հիմնված պարզ ձևը կհաջողվի: Ահա թե ինչ տեսք կունենա այն.

Եվ ահա այս ձևի կոդը.




Նոր օգտվողի գրանցում



Նոր օգտվողի գրանցման ձև

* ցույց է տալիս պարտադիր դաշտը


Նպատակն այժմ պարզ է, ես ձեզ կներկայացնեմ accesscontrol.php-ի կոդը: Սկսեք ներառելով ձեր երկու հարմար ներառող ֆայլերը.