HTTP是無狀態協議。當瀏覽器中加載頁面,而後轉到同一網站的另外一頁面時,服務器和瀏覽器都沒有任何內在的方法能夠認識到,這是同一瀏覽器訪問同一網站。換一種說法,Web工做的方式就是在每一個HTTP請求中都要包含全部必要的信息,服務器才能知足這個請求。前端
因此須要用某種辦法在HTTP上創建狀態,因而便有了cookie和會話。git
cookie的想法很簡單:服務器發送一點信息,瀏覽器在一段可配置的時期內保存它。發送哪些信息確實是由服務器來決定:一般只是一個惟一ID號,標識特定瀏覽器,從而維持一個有狀態的假象。數據庫
cookie對用戶來講不是加密的;express
服務器向客戶端發送的全部cookie都能被客戶端查看。npm
用戶對cookie有絕對的控制權,能夠刪除或禁用cookiejson
通常的cookie能夠被篡改bootstrap
無論瀏覽器何時發起一個跟cookie關聯的請求,盲目地相信cookie中的內容,都有可能會受到攻擊。要確保cookie不被篡改,使用簽名cookie。瀏覽器
用簽名cookie會有幫助(不論是用戶修改的仍是惡意JavaScript修改的,這些篡改都會在簽名cookie中留下明顯的痕跡),而且還能夠設定選項指明cookie只能由服務器修改。這些cookie的用途會受限,但它們確定更安全。**安全
若是能夠選擇,會話要優於cookie服務器
大多數狀況下,能夠用會話維持狀態; 而且會話更容易,不用擔憂會濫用用戶的存儲,並且也更安全。
當服務器但願客戶端保存一個cookie時,它會發送一個響應頭Set-Cookie
,其中包含名稱/值對。當客戶端向服務器發送含有cookie的請求時,它會發送多個請求頭Cookie,其中包含這些cookie的值。**
推薦用一個隨機密碼生成器來生成cookie祕鑰。**
外化第三方憑證是一種常見的作法,好比cookie祕鑰、數據庫密碼和API令牌(Twitter、Facebook等)。這不只易於維護(容易找到和更新憑證),還可讓版本控制系統忽略這些憑證文件。這對放在GitHub或其餘開源源碼控制庫上的開源代碼庫尤爲重要。**
所以能夠準備將憑證外化在一個JavaScript文件中(用JSON或XML也行,但我以爲JavaScript最容易)。
建立文件credentials.js:
module.exports = { cookieSecret: '把你的cookie祕鑰放在這裏', };
var credentials = require('./credentials.js');
在程序中開始設置和訪問cookie以前,須要先引入中間件cookie-parser
。首先npm install --save cookie-parser
,而後
app.use(require('cookie-parser')(credentials.cookieSecret));
res.cookie('monster', 'nom nom'); res.cookie('signed_monster', 'nom nom', { signed: true });
簽名cookie的優先級高於未簽名cookie。若是將簽名cookie命名爲signed_monster
,那就不能用這個名字再命名未簽名cookie(它返回時會變成undefined)。
要獲取客戶端發送過來的cookie的值(若是有的話),只需訪問請求對象的cookie或signedCookie
屬性:
var monster = req.cookies.monster; var signedMonster = req.signedCookies.monster;
任何字符串均可以做爲cookie的名稱。
要刪除cookie,用res.clearCookie
:
res.clearCookie('monster');
domain
控制跟cookie關聯的域名。這樣能夠將cookie分配給特定的子域名。注意,不能給cookie設置跟服務器所用域名不一樣的域名,由於那樣它什麼也不會作。
path
控制應用這個cookie的路徑。注意,路徑會隱含地通配其後的路徑。若是用的路徑是/ (默認值),它會應用到網站的全部頁面上。若是用的路徑是/foo,它會應用到/foo、/foo/bar等路徑上。
maxAge
指定客戶端應該保存cookie多長時間,單位是毫秒。若是你省略了這一選項,瀏覽器關閉時cookie就會被刪掉。(也能夠用expiration指定cookie過時的日期,但語法很麻煩。建議用maxAge。)
secure
指定該cookie只經過安全(HTTPS)鏈接發送。
httpOnly
將這個選項設爲true代表這個cookie只能由服務器修改。也就是說客戶端JavaScript不能修改它。這有助於防範XSS攻擊。
signed
設爲true會對這個cookie簽名,這樣就須要用res.signedCookies
而不是res.cookies
訪問它。被篡改的簽名cookie會被服務器拒絕,而且cookie值會重置爲它的原始值。
HTML5爲會話提供了另外一種選擇,那就是本地存儲,但如今尚未使人歎服的理由去採用這種技術而放棄通過驗證有效的cookie。
然而出於開發和測試的須要,有它就足夠了。
首先安裝express-session(npm install --save express-sessio
n)
而後,在鏈入cookie-parser以後鏈入express-session:
app.use(require('cookie-parser')(credentials.cookieSecret)); app.use(require('express-session')());
中間件express-session接受帶有以下選項的配置對象:
key
存放惟一會話標識的cookie名稱。默認爲connect.sid
。
store
會話存儲的實例。默認爲一個MemoryStore的實例,能夠知足咱們當前的要求。
cookie
會話cookie的cookie設置 (path
、domain
、secure
等)。適用於常規的cookie默認值。
req.session.userName = 'Anonymous'; var colorScheme = req.session.colorScheme || 'dark';
delete
操做符:req.session.userName = null; // 這會將'userName'設爲null;但不會移除它 delete req.session.colorScheme; // 這會移除'colorScheme'
//使用了bootstrap {{#if flash}} <div class="alert alert-dismissible alert-{{flash.type}}"> <button type="button" class="close" data-dismiss="alert" aria-hidden="true">×</button> <strong>{{flash.intro}}</strong> {{{flash.message}}} </div> {{/if}}
app.use(function(req, res, next){ // 若是有即顯消息,把它傳到上下文中,而後清除它 res.locals.flash = req.session.flash; delete req.session.flash; next(); });
接下來咱們看一下如何使用即顯消息。假設咱們的用戶訂閱了簡報,而且咱們想在用戶訂閱後把他們重定向到簡報歸檔頁面去。咱們的表單處理器多是這樣的:
app.post('/newsletter', function(req, res){ var name = req.body.name || '', email = req.body.email || ''; // 輸入驗證 if(!email.match(VALID_EMAIL_REGEX)) { if(req.xhr) return res.json({ error: 'Invalid name email address.' }); req.session.flash = { type: 'danger', intro: 'Validation error!', message: 'The email address you entered was not valid.', }; return res.redirect(303, '/newsletter/archive'); } new NewsletterSignup({ name: name, email: email }).save(function(err){ if(err) { if(req.xhr) return res.json({ error: 'Database error.' }); req.session.flash = { type: 'danger', intro: 'Database error!', message: 'There was a database error; please try again later.', } return res.redirect(303, '/newsletter/archive'); } if(req.xhr) return res.json({ success: true }); req.session.flash = { type: 'success', intro: 'Thank you!', message: 'You have now been signed up for the newsletter.', }; return res.redirect(303, '/newsletter/archive'); }); });
看如何用同一個處理器處理AJAX提交(由於咱們檢查了req.xhr),而且區分開了輸入驗證錯誤和數據庫錯誤。記住,即使在前端作了輸入驗證,在後臺也應該再作一次。
即顯消息是網站中一種很棒的機制,即使在某些特定區域其餘方法更合適一些(好比,即顯消息在多表單「嚮導」或購物車結帳流程中就不太合適)。
由於在中間件裏把即顯消息從會話中傳給了res.locals.flash
,因此必須執行重定向以便顯示即顯消息。若是不想經過重定向顯示即顯消息,直接設定res.locals.flash
,而不是req.session.flash
。