1、什麼是session?html
最近在學習node.js 的express框架,接觸到了關於session方面的內容。翻閱了一些的博客,學到了很多東西,發現一篇博文講的很好,概念內容摘抄以下:node
Session是什麼 Session通常譯做會話,牛津詞典對其的解釋是進行某活動連續的一段時間。從不一樣的層面看待session,它有着相似但不全然相同的含義。好比,在web應用的用戶看來,他打開瀏覽器訪問一個電子商務網站,登陸、並完成購物直到關閉瀏覽器,這是一個會話。而在web應用的開發者開來,用戶登陸時我須要建立一個數據結構以存儲用戶的登陸信息,這個結構也叫作session。所以在談論session的時候要注意上下文環境。而本文談論的是一種基於HTTP協議的用以加強web應用能力的機制或者說一種方案,它不是單指某種特定的動態頁面技術,而這種能力就是保持狀態,也能夠稱做保持會話。 爲何須要session 談及session通常是在web應用的背景之下,咱們知道web應用是基於HTTP協議的,而HTTP協議偏偏是一種無狀態協議。也就是說,用戶從A頁面跳轉到B頁面會從新發送一次HTTP請求,而服務端在返回響應的時候是沒法獲知該用戶在請求B頁面以前作了什麼的。 對於HTTP的無狀態性的緣由,相關RFC裏並無解釋,但聯繫到HTTP的歷史以及應用場景,咱們能夠推測出一些理由: 1. 設計HTTP最初的目的是爲了提供一種發佈和接收HTML頁面的方法。那個時候沒有動態頁面技術,只有純粹的靜態HTML頁面,所以根本不須要協議能保持狀態; 2. 用戶在收到響應時,每每要花一些時間來閱讀頁面,所以若是保持客戶端和服務端之間的鏈接,那麼這個鏈接在大多數的時間裏都將是空閒的,這是一種資源的無故浪費。因此HTTP原始的設計是默認短鏈接,即客戶端和服務端完成一次請求和響應以後就斷開TCP鏈接,服務器所以沒法預知客戶端的下一個動做,它甚至都不知道這個用戶會不會再次訪問,所以讓HTTP協議來維護用戶的訪問狀態也全然沒有必要; 3. 將一部分複雜性轉嫁到以HTTP協議爲基礎的技術之上可使得HTTP在協議這個層面上顯得相對簡單,而這種簡單也賦予了HTTP更強的擴展能力。事實上,session技術從本質上來說也是對HTTP協議的一種擴展。 總而言之,HTTP的無狀態是由其歷史使命而決定的。但隨着網絡技術的蓬勃發展,人們不再知足於死板乏味的靜態HTML,他們但願web應用能動起來,因而客戶端出現了腳本和DOM技術,HTML裏增長了表單,而服務端出現了CGI等等動態技術。 而正是這種web動態化的需求,給HTTP協議提出了一個難題:一個無狀態的協議怎樣才能關聯兩次連續的請求呢?也就是說無狀態的協議怎樣才能知足有狀態的需求呢? 此時有狀態是必然趨勢而協議的無狀態性也是木已成舟,所以咱們須要一些方案來解決這個矛盾,來保持HTTP鏈接狀態,因而出現了cookie和session。 對於此部份內容,讀者或許會有一些疑問,筆者在此先談兩點: 1. 無狀態性和長鏈接 可能有人會問,如今被普遍使用的HTTP1.1默認使用長鏈接,它仍是無狀態的嗎? 鏈接方式和有無狀態是徹底沒有關係的兩回事。由於狀態從某種意義上來說就是數據,而鏈接方式只是決定了數據的傳輸方式,而不能決定數據。長鏈接是隨着計算機性能的提升和網絡環境的改善所採起的一種合理的性能上的優化,通常狀況下,web服務器會對長鏈接的數量進行限制,以避免資源的過分消耗。 2. 無狀態性和session Session是有狀態的,而HTTP協議是無狀態的,兩者是否矛盾呢? Session和HTTP協議屬於不一樣層面的事物,後者屬於ISO七層模型的最高層應用層,前者不屬於後者,前者是具體的動態頁面技術來實現的,但同時它又是基於後者的。在下文中筆者會分析Servlet/Jsp技術中的session機制,這會使你對此有更深入的理解。
Cookie和Session 上面提到解決HTTP協議自身無狀態的方式有cookie和session。兩者都能記錄狀態,前者是將狀態數據保存在客戶端,後者則保存在服務端。 首先看一下cookie的工做原理,這須要有基本的HTTP協議基礎。 cookie是在RFC2109(已廢棄,被RFC2965取代)裏初次被描述的,每一個客戶端最多保持三百個cookie,每一個域名下最多20個Cookie(實際上通常瀏覽器如今都比這個多,如Firefox是50個),而每一個cookie的大小爲最多4K,不過不一樣的瀏覽器都有各自的實現。對於cookie的使用,最重要的就是要控制cookie的大小,不要放入無用的信息,也不要放入過多信息。 不管使用何種服務端技術,只要發送回的HTTP響應中包含以下形式的頭,則視爲服務器要求設置一個cookie: Set-cookie:name=name;expires=date;path=path;domain=domain 支持cookie的瀏覽器都會對此做出反應,即建立cookie文件並保存(也多是內存cookie),用戶之後在每次發出請求時,瀏覽器都要判斷當前全部的cookie中有沒有沒失效(根據expires屬性判斷)而且匹配了path屬性的cookie信息,若是有的話,會如下面的形式加入到請求頭中發回服務端: Cookie: name="zj"; Path="/linkage" 服務端的動態腳本會對其進行分析,並作出相應的處理,固然也能夠選擇直接忽略。 這裏牽扯到一個規範(或協議)與實現的問題,簡單來說就是規範規定了作成什麼樣子,那麼實現就必須依據規範來作,這樣才能互相兼容,可是各個實現所使用的方式卻不受約束,也能夠在實現了規範的基礎上超出規範,這就稱之爲擴展了。不管哪一種瀏覽器,只要想提供cookie的功能,那就必須依照相應的RFC規範來實現。因此這裏服務器只管發Set-cookie頭域,這也是HTTP協議無狀態性的一種體現。 須要注意的是,出於安全性的考慮,cookie能夠被瀏覽器禁用。 再看一下session的原理: 筆者沒有找到相關的RFC,由於session本就不是協議層面的事物。它的基本原理是服務端爲每個session維護一份會話信息數據,而客戶端和服務端依靠一個全局惟一的標識來訪問會話信息數據。用戶訪問web應用時,服務端程序決定什麼時候建立session,建立session能夠歸納爲三個步驟: 1. 生成全局惟一標識符(sessionid); 2. 開闢數據存儲空間。通常會在內存中建立相應的數據結構,但這種狀況下,系統一旦掉電,全部的會話數據就會丟失,若是是電子商務網站,這種事故會形成嚴重的後果。不過也能夠寫到文件裏甚至存儲在數據庫中,這樣雖然會增長I/O開銷,但session能夠實現某種程度的持久化,並且更有利於session的共享;
3. 將session的全局惟一標示符發送給客戶端。 問題的關鍵就在服務端如何發送這個session的惟一標識上。聯繫到HTTP協議,數據無非能夠放到請求行、頭域或Body裏,基於此,通常來講會有兩種經常使用的方式:cookie和URL重寫。 1. Cookie 讀者應該想到了,對,服務端只要設置Set-cookie頭就能夠將session的標識符傳送到客戶端,而客戶端此後的每一次請求都會帶上這個標識符,因爲cookie能夠設置失效時間,因此通常包含session信息的cookie會設置失效時間爲0,即瀏覽器進程有效時間。至於瀏覽器怎麼處理這個0,每一個瀏覽器都有本身的方案,但差異都不會太大(通常體如今新建瀏覽器窗口的時候); 2. URL重寫 所謂URL重寫,顧名思義就是重寫URL。試想,在返回用戶請求的頁面以前,將頁面內全部的URL後面所有以get參數的方式加上session標識符(或者加在path info部分等等),這樣用戶在收到響應以後,不管點擊哪一個連接或提交表單,都會在再帶上session的標識符,從而就實現了會話的保持。讀者可能會以爲這種作法比較麻煩,確實是這樣,可是,若是客戶端禁用了cookie的話,URL重寫將會是首選。 到這裏,讀者應該明白我前面爲何說session也算做是對HTTP的一種擴展了吧。以下兩幅圖是筆者在Firefox的Firebug插件中的截圖,能夠看到,當我第一次訪問index.jsp時,響應頭裏包含了Set-cookie頭,而請求頭中沒有。當我再次刷新頁面時,圖二顯示在響應中不在有Set-cookie頭,而在請求頭中卻有了Cookie頭。注意一下Cookie的名字:jsessionid,顧名思義,就是session的標識符,另外能夠看到兩幅圖中的jsessionid的值是相同的,緣由筆者就再也不多解釋了。另外讀者可能在一些網站上見過在最後附加了一段形如jsessionid=xxx的URL,這就是採用URL重寫來實現的session。 ![](http://static.javashuo.com/static/loading.gif)
(圖一,首次請求index.jsp)
(圖二,再次請求index.jsp) Cookie和session因爲實現手段不一樣,所以也各有優缺點和各自的應用場景: 1. 應用場景 Cookie的典型應用場景是Remember Me服務,即用戶的帳戶信息經過cookie的形式保存在客戶端,當用戶再次請求匹配的URL的時候,帳戶信息會被傳送到服務端,交由相應的程序完成自動登陸等功能。固然也能夠保存一些客戶端信息,好比頁面佈局以及搜索歷史等等。 Session的典型應用場景是用戶登陸某網站以後,將其登陸信息放入session,在之後的每次請求中查詢相應的登陸信息以確保該用戶合法。固然仍是有購物車等等經典場景; 2. 安全性 cookie將信息保存在客戶端,若是不進行加密的話,無疑會暴露一些隱私信息,安全性不好,通常狀況下敏感信息是通過加密後存儲在cookie中,但很容易就會被竊取。而session只會將信息存儲在服務端,若是存儲在文件或數據庫中,也有被竊取的可能,只是可能性比cookie小了太多。 Session安全性方面比較突出的是存在會話劫持的問題,這是一種安全威脅,這在下文會進行更詳細的說明。整體來說,session的安全性要高於cookie; 3. 性能 Cookie存儲在客戶端,消耗的是客戶端的I/O和內存,而session存儲在服務端,消耗的是服務端的資源。可是session對服務器形成的壓力比較集中,而cookie很好地分散了資源消耗,就這點來講,cookie是要優於session的;
4. 時效性 Cookie能夠經過設置有效期使其較長時間內存在於客戶端,而session通常只有比較短的有效期(用戶主動銷燬session或關閉瀏覽器後引起超時); 5. 其餘 Cookie的處理在開發中沒有session方便。並且cookie在客戶端是有數量和大小的限制的,而session的大小卻只以硬件爲限制,能存儲的數據無疑大了太多。
後文中我會主要針對express的session專門講解。主要參考的博客網址以下,並對博主的無私奉獻表示萬分感謝。git
http://www.cnblogs.com/shoru/archive/2010/02/19/1669395.html (大話session)github
http://blog.csdn.net/fangaoxin/article/details/6952954 (Cookie/Session機制詳解)web
2、express框架之session 內存存儲mongodb
express-session 是基於express框專門用於處理session的中間件。這裏不談express-session怎麼安裝,只給出相應的實例代碼。另外,session的認證機制離不開cookie,須要同時使用cookieParser 中間件,有關的介紹能夠專門參考https://github.com/expressjs/session/blob/master/README.md,或者參考http://blog.modulus.io/nodejs-and-express-sessions,這個博客上講的比較清楚。數據庫
1 var express = require('express'); 2 var session = require('express-session'); 3 var cookieParser = require('cookie-parser'); 4 5 var app = express(); 6 7 app.use(cookieParser()); 8 app.use(session({ 9 secret: '12345', 10 name: 'testapp', //這裏的name值得是cookie的name,默認cookie的name是:connect.sid 11 cookie: {maxAge: 80000 }, //設置maxAge是80000ms,即80s後session和相應的cookie失效過時 12 resave: false, 13 saveUninitialized: true, 14 })); 15 16 17 app.get('/awesome', function(req, res){ 18 19 if(req.session.lastPage) { 20 console.log('Last page was: ' + req.session.lastPage + "."); 21 } 22 req.session.lastPage = '/awesome'; //每一次訪問時,session對象的lastPage會自動的保存或更新內存中的session中去。 23 res.send("You're Awesome. And the session expired time is: " + req.session.cookie.maxAge); 24 }); 25 26 app.get('/radical', function(req, res){ 27 if (req.session.lastPage) { 28 console.log('Last page was: ' + req.session.lastPage + "."); 29 } 30 req.session.lastPage = '/radical'; 31 res.send('What a radical visit! And the session expired time is: ' + req.session.cookie.maxAge); 32 }); 33 34 app.get('/tubular', function(req, res){ 35 if (req.session.lastPage){ 36 console.log("Last page was: " + req.session.lastPage + "."); 37 } 38 39 req.session.lastPage = '/tubular'; 40 res.send('Are you a suffer? And the session expired time is: ' + req.session.cookie.maxAge); 41 }); 42 43 44 app.listen(5000);
2.1 express-session中間件的使用:express
只須要用express app的use方法將session掛載在‘/’路徑便可,這樣全部的路由均可以訪問到session。能夠給要掛載的session傳遞不一樣的option參數,來控制session的不一樣特性。具體能夠參見官網:https://github.com/expressjs/session/blob/master/README.md。瀏覽器
2.2 session內容的存儲和更改:安全
To store or access session data, simply use the request property req.session, which is (generally) serialized as JSON by the store, so nested objects are typically fine.
一旦咱們將express-session中間件用use掛載後,咱們能夠很方便的經過req參數來存儲和訪問session對象的數據。req.session是一個JSON格式的JavaScript對象,咱們能夠在使用的過程當中隨意的增長成員,這些成員會自動的被保存到option參數指定的地方,默認即爲內存中去。
2.3 session的生命週期
session與發送到客戶端瀏覽器的生命週期是一致的。而咱們在掛載session的時候,經過option選項的cookie.maxAge成員,咱們能夠設置session的過時時間,以ms爲單位(可是,若是session存儲在mongodb中的話,任何低於60s(60000ms)的設置是沒有用的,下文會有詳細的解釋)。若是maxAge不設置,默認爲null,這樣的expire的時間就是瀏覽器的關閉時間,即每次關閉瀏覽器的時候,session都會失效。
3、express框架之session 數據庫存儲
有時候,咱們須要session的聲明週期要長一點,好比好多網站有個免密碼兩週內自動登陸的功能。基於這個需求,session必須尋找內存以外的存儲載體,數據庫能提供完美的解決方案。這裏,我選用的是mongodb數據庫,做爲一個NoSQL數據庫,它的基礎數據對象時database-collection-document 對象模型很是直觀並易於理解,針對node.js 也提供了豐富的驅動和API。express框架提供了針對mongodb的中間件:connect-mongo,咱們只需在掛載session的時候在options中傳入mongodb的參數便可,程序運行的時候, express app 會自動的替咱們管理session的存儲,更新和刪除。具體能夠參考:
https://github.com/kcbanner/connect-mongo
測試代碼以下:
1 var express = require('express'); 2 var session = require('express-session'); 3 var cookieParser = require('cookie-parser'); 4 var MongoStore = require('connect-mongo')(session); 5 var app = express(); 6 7 app.use(cookieParser()); 8 app.use(session({ 9 secret: '12345', 10 name: 'testapp', 11 cookie: {maxAge: 80000 }, 12 resave: false, 13 saveUninitialized: true, 14 store: new MongoStore({ //建立新的mongodb數據庫 15 host: 'localhost', //數據庫的地址,本機的話就是127.0.0.1,也能夠是網絡主機 16 port: 27017, //數據庫的端口號 17 db: 'test-app' //數據庫的名稱。 18 }) 19 })); 20 21 22 app.get('/awesome', function(req, res){ 23 24 if(req.session.lastPage) { 25 console.log('Last page was: ' + req.session.lastPage + "."); 26 } 27 req.session.lastPage = '/awesome'; 28 res.send("You're Awesome. And the session expired time is: " + req.session.cookie.maxAge); 29 }); 30 31 app.get('/radical', function(req, res){ 32 if (req.session.lastPage) { 33 console.log('Last page was: ' + req.session.lastPage + "."); 34 } 35 req.session.lastPage = '/radical'; 36 res.send('What a radical visit! And the session expired time is: ' + req.session.cookie.maxAge); 37 }); 38 39 app.get('/tubular', function(req, res){ 40 if (req.session.lastPage){ 41 console.log("Last page was: " + req.session.lastPage + "."); 42 } 43 44 req.session.lastPage = '/tubular'; 45 res.send('Are you a suffer? And the session expired time is: ' + req.session.cookie.maxAge); 46 }); 47 48 49 app.listen(5000);
跟session的內存存儲同樣,只需增長紅色部分的store選項便可,app會自動替咱們把session存入到mongodb數據,而非內存中。
3.1 session的生命週期:
因爲session是存在服務器端數據庫的,因此的它的生命週期能夠持久化,而不只限於瀏覽器關閉的時間。具體是由cookie.maxAge 決定:若是maxAge設定是1個小時,那麼從這個因瀏覽器訪問服務器致使session建立開始後,session會一直保存在服務器端,即便瀏覽器關閉,session也會繼續存在。若是此時服務器宕機,只要開機後數據庫沒發生不可逆轉的破壞,maxAge時間沒過時,那麼session是能夠繼續保持的。
當maxAge時間過時後,session會自動的數據庫中移除,對應的還有瀏覽器的cookie。不過,因爲connect-mongo的特殊機制(每1分鐘檢查一次過時session),session的移除可能在時間上會有必定的滯後。
connect-mongo uses MongoDB's TTL collection feature (2.2+) to have mongod automatically remove expired sessions. (mongod runs this check every minute.) Note: By connect/express's default, session cookies are set to expire when the user closes their browser (maxAge: null). In accordance with standard industry practices, connect-mongo will set these sessions to expire two weeks from their last 'set'. You can override this behavior by manually setting the maxAge for your cookies -- just keep in mind that any value less than 60 seconds is pointless, as mongod will only delete expired documents in a TTL collection every minute.
固然,因爲cookie是由瀏覽器廠商實現的,cookie不具備跨瀏覽器的特性,例如,我用firefox瀏覽器在京東上購物時,勾選了2周內免密碼輸入,可是當我第一次用IE登錄京東時,一樣要從新輸入密碼。因此,這對服務器的同一個操做,不一樣的瀏覽器發起的請求,會產生不一樣的session-cookie。