首頁  >  文章  >  後端開發  >  PHP Session原理分析及使用

PHP Session原理分析及使用

巴扎黑
巴扎黑原創
2016-12-05 09:53:391468瀏覽

之前在一個叫魔法實驗室的部落格中看過一篇《php session原理徹底分析》的文章,作者從session的使用角度很好闡述了在代碼運行過程中,每個環節的變化以及相關參數的設定及作用。本來想把原文轉帖過來,但是原部落格被關閉了。不知是這次大範圍的重新備案,還是其他什麼原因所致。透過百度快照找到一些原文資料,沒找到的將按先前的理解重新整理,以使大家對session能有更多了解。

楔子:Session大白話 

Session,英文翻譯為“會話”,兩個人聊天,從第一句問好,到最後一句再見,這就構成了一個會話。 PHP裡的session主要是指客戶端瀏覽器與服務端資料交換的對話,從瀏覽器開啟到關閉,一個最簡單的會話週期。電腦語言一般怎麼實作會話呢?舉個通俗的例子:
服務端好比一個理髮店,客戶端好比每一個去理髮的客人,很多理髮店都有這種促銷手段,連續消費10次的客人,可以免費一次,大概有三種方式來實現:
1、理髮師傅記性太好,你來過幾次,他看一眼就知道——這叫協議本身支持會話;
2、每個客人發一個會員卡,你每次消費,都要帶著這張卡片,消費一次記錄一筆,當然還要加蓋印章——這叫透過cookie實現會話,缺點是安全性不高,我完全可以偽造會員卡或者公章;
3、理髮店準備一個大帳本,客人每人對應一個會員號碼或自己的個人資料,甚至密碼,每個客人來消費,報一下自己的會員號,再把消費次數記錄到大帳本裡——這就是session實現會話,客人腦中的會員號碼就是保存在客戶端的SESSIONID,大帳本就是保存在服務端的session數據,這樣相比第二種方法,安全性要高很多,除非你說你把自己的會員號碼和密碼都搞丟了,這叫做偽造客戶端的SESSIONID。
 
因為http協定是無狀態的,所以php要實作會話只能透過後面兩種方式,前一種cookie,缺點已經說了,安全性不高,所以重要的會話會選擇使用session。 session會話必須依靠一個標識,也可以理解成一個暗號,就是SESSIONID。這是個經過加密的串,保存在客戶端,通常在cookie裡,客戶端與服務端的每次交流都是透過這個SESSIONID,客戶端先自報家門,伺服器才能找到你在服務端保存的會話數據,繼續通話。

php.ini常用session設定 

[服務端]
session.save_handler = files
預設為file,定義session在服務端的保存方式,file意為把sesion保存到一個臨時檔案裡,如果我們想自訂別的方式保存(例如用資料庫),則需要把該項目設定為user;
 
session.save_path = "/tmp/"
定義服務端儲存session的暫存檔案的位置。
 
session.auto_start = 0
如置1,則不用在每個檔案裡寫session_start(); session自動start。
 
session.gc_probability = 1
session.gc_divisor = 100
session.gc_maxlifetime = 1440
這三個配置組合構建服務端session的垃圾回收機制.gc_probability與dision.gsession 構成的執行理論,構成gsesc_prob解釋為服務端定期有一定的機率呼叫gc函數來對session進行清理,清理的機率為:gc_probability/gc_divisor 例如:1/100  表示每一個新會話初始化時,有1%的機率會啟動垃圾回收程序,清理的標準為session.gc_maxlifetime定義的時間。
 
[客戶端]
session.use_cookies = 1
sessionid在客戶端採用的儲存方式,置1代表使用cookie記錄客戶端的sessionid,同時,$_COOKIE變數裡才會有$_COOKIE['PHPSESSIONID']這個元素存在;
 
session.use_only_cookies = 1
也是定義sessionid在客戶端採用的儲存方式,置1代表僅使用cookie 來存放會話ID。一般來說,現在客戶端都會支援cookie,所以建議設定成1,這樣可以防止有關透過 URL 傳遞會話 ID 的攻擊。
 
session.use_trans_sid = 0
相對應於上面那個設置,這裡如果置1,則代表允許sessionid透過url參數傳遞,同理,建議設定成0;
 
session.referer_check =s 
= 1的時候才會生效,目的是檢查HTTP頭中的"Referer"以判斷包含於URL中的會話id是否有效,HTTP_REFERER必須包含這個參數指定的字串,否則URL中的會話id將被視為無效。所以一般預設為空,即不檢查。  
 
session.name = PHPSESSID
定義sessionid的名稱,即變數名,透過瀏覽器http工具可以查看PHPSESSID的值;
 
session.hash_function = 0
選擇session_name的加密方式,0代表md5加密,1代表sha1加密,預設是0,但是據說用sha1方式加密,安全性更高;
 
session.hash_bits_per_character = 4
指定在session_namename字串中的每個字元內保存多少位二進制數,這些二進制數是hash函數的運算結果。
4   bits:   0-9,   a-f  
5   bits:   0-9,   a-v  
6   
url_rewriter.tags = "a=href,area= href,frame=src,input=src,form=,fieldset="
指定重寫哪些HTML標籤來包含sid(session_id)(僅在"session.use_trans_sid"開啟的情況下有效),URL重寫器將會加入一個隱藏的"",它包含了本應額外追加到URL上的信息。
 
session.cookie_lifetime = 0
保存sessionid的cookie檔案的生命週期,如置0,代表會話結束,則sessionid就自動消失,常見的強行關閉瀏覽器,就會遺失上一次的sessionid; 
session. cookie_path = /
保存sessionid的cookie檔案在客戶端的位置;
 
session.cookie_domain = /
保存sessionid的cookie的網域設置,這跟cookie允許的網域的權限設定有關,一般來說想讓自己網站所有的目錄都能存取到客戶端的cookie,就應該設定成「/」如需要詳細了解,可以看下setcookie()函數的domain參數相關設定和使用方法;
 
session.bug_compat_42 = 1
session.bug_compat_warn = 1
這兩個可以說幾乎是快要被廢棄的設置,是為了老版本的php服務的,主要是針對session_register函數,因為php5的register_global預設是關閉狀態,所以在php5裡根本用不到session_register這個函數;而且php6就要廢除這個設置,直接定義為關閉,所以沒必要研究這兩個了;


session_start()做了些什麼?

假設php.ini中session的幾個關鍵參數配置為:
session.save_handler = files
session.use_cookies = 1
session.name = PHPSESSIDsession.save_path = "/tmp/"程式碼範例闡述,在一個會話過程中session_start的作用。

程式1:
session_start();

$_SESSION['uname'] = 'monkey';

$_SESSION['ukey'] = 20119999;

>
? ()會做兩件事:


1、在客戶端產生一個存放PHPSESSID的cookie文件,這個文件的存放位置和存放方式跟程式的執行方式有關,不同的瀏覽器也不盡相同,這一步會產生一個序列化後的字串-PHPSESSID;查看瀏覽器中的cookie訊息,可以安裝相關插件。 firefox中httpfox,web developer等都是很好的工具。


2、在服務端產生一個存放session資料的暫存文件,存放的位置由session.save_path參數指定,名稱類似“sess_85891d6a81ab13965d349bde29b2306c”,“sess1ab13965d349bde29b2306c”,“sess_v. 6c」即此次會話的PHPSESSID,跟客戶端的PHPSESSID值是一樣的。

用編輯器開啟「sess_85891d6a81ab13965d349bde29b2306c」文件,會看到一串「uname|s:6:"monkey";ukey|i:20119999;」這樣的內容。這個文件裡存放的就是$_SESSION變數的具體內容,每個變數用「;"分號隔開。


格式為:變數名稱| 變數型別: [長度] : 值; 例如: uname|s:6:"monkey"; 表示SESSION變數uname的型別為字串,值長度為6,值為monkey.


那麼問題來了,上面說的兩件事,是在程式執行到session_start(),就完成的嗎?這兩件事,誰先誰後呢?

讓試驗來證明,稍微改變程序:


程序2:
session_start(); 

$_SESSION['uname'] = 'monkey';
$_SESSION['ukey'] = 201199999;
sleep(30); 
?>  

先把客戶端和服務端的session資料通通刪除,然後執行程式2,趁著程式裡的sleep30秒的工夫,去查看客戶端和服務端的session狀況,發現:在程式執行過程中,客戶端並沒有建立保存PHPSESSID的cookie文件,服務端卻已經有了保存session內容的臨時文件,但是文件裡沒有內容,等30秒時間過了之後,客戶端的cookie文件才會生成,服務端的session檔案裡才有了內容。


由此推斷大致流程應該為:在程式執行到session_start()的時候,服務端先產生PHPSESSID,並產生相對應的session文件,但是在程式進行$_SESSION賦值的時候,並沒有把對應的值寫入到session文件裡,姑且臆斷為保存在內存裡吧,到了程序執行完畢後,才會在客戶端生成保存PHPSESSID的cookie文件,並把$_SESSION變量裡的值寫入服務端的session文件裡,至於最後兩個步驟誰先誰後,暫時還沒想到好方法來證明。
 

為了更進一步論證,刪除客戶端和服務端的session相關內容執行程序3,觀察第一次和第二次的結果:
程序3:
session_start(); 
$_SESSION[' uname'] = 'monkey'; 
$session_id = session_id(); 
$sess_file = "/tmp/sess_".$session_id;
$content = file_get_contents($sess_file); 
_COOKIE['PHPSESSID'] .'***';
echo '
' . $_SESSION['uname'] . '
'; 
echo '***'.$content.'* **'; 
?>  
 

上面說的是第一次sessin_start()的執行方式,也就是一套程式裡,第一個session_start()出現的時候所做的事情,下面來看之後的session_start(): 

假設的php.ini配置:session.cookie_lifetime = 0        

程式4:
session_start()); ];
?>  

現在,客戶端已經有了保存PHPSESSID的cookie文件,服務端也有了保存session內容的sess_文件,執行程式4,會列印出正常的內容。這時,如果強行關閉瀏覽器,再執行程式4,結果會怎麼樣呢?

 

首先,session.cookie_lifetime設定成0,表示客戶端保存PHPSESSID的cookie檔案的生存週期為0,瀏覽器如果處於開啟狀態,PHPSESSID的值會保存在記憶體中,一旦強行關閉,保存PHPSESSID的開啟狀態,PHPSESSID的值會保存在記憶體中,一旦強行關閉,儲存PHPSESSID的cookie檔案會同時銷毀,但是服務端並沒有執行session_destroy(),所以,服務端的session資料檔還在,但是當瀏覽器再次開啟執行程式4,發現什麼都沒有輸出,由此推理:

session_start ()首先會去取得客戶端cookie裡的PHPSESSID,然後與「sess_」組成文件名,去服務端查找這個文件,然後取出文件裡的內容,把內容放到$_SESSION全域變數里以供使用。瀏覽器強行關閉,再打開,之前的PHPSESSID遺失,這時遇到session_start()就相當於上面說的第一次執行,會產生一個新的PHPSESSID,這個PHPSESSID匹配不到之前那個服務端的sess_文件,所以取不到內容。當然,服務端也有能跟這個PHPSESSID相符的文件,不過,那個文件還是空的。


所以,有的系統為了實現同一用戶只能在一台機器甚至一個瀏覽器登入的​​機制,如果沒有修改session.cookie_lifetime的設置,就會出現強行關閉瀏覽器之後,在服務端session生存期截止前該,使用者登入不進去的情況,比較好的方法是把session.cookie_lifetime設定成一個比較大的值,反正一個cookie檔案存在時間久一些也沒什麼影響。


陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn