nodeJS之Cookie和Session(一)
一:Cookie
HTTP是一個無狀態協議,客戶端每次發出請求時候,下一次請求得不到上一次請求的數據,那么如何將上一次請求和下一次請求的數據關聯起來呢?
比如登錄官網后,再切換到其他頁面時候,那么其他的頁面是如何知道該用戶已經登錄了呢?所以這就可以使用到cookie中的值來判斷了。
cookie它是一個由瀏覽器和服務器共同協作實現的協議的。那么cookie分為如下幾步實現:
1. 服務器端向客戶端發送cookie。
2. 瀏覽器將cookie保存。
3. 之后每次請求都會將cookie發向服務器端。
1.1 服務器端發送cookie
服務器發送cookie給客戶端是通過HTTP響應報文實現的。在set-Cookie中設置給客戶端發送的cookie,cookie格式如下:
Set-Cookie: name=value; Max-Age=60; Path=/; domain=.domain.com;Expires=Sun, 27 May 2018 05:44:24 GMT; HttpOnly, secure;
如下圖所示
其中name=value是必選項,其他都是可選的,cookie主要構成如下:
name: 一個唯一確定cookie的名稱。
value: 存儲在cookie中字符串的值。
domain: cookie對於那個域下是有效的,
path: 表示這個cookie影響到的路徑,瀏覽器會根據這個配置,向指定的域中匹配的路徑發送cookie。
expires: 失效時間,表示cookie何時失效的時間,如果不設置這個時間,瀏覽器就會在頁面關閉時將刪除所有的cookie,不過我們也可以自己設置過期時間。
注意:如果客戶端和服務器端設置的時間不一致,使用expires就會存在偏差。
max-age: 用來告訴瀏覽器此cookie多久過期(單位是秒),一般的情況下,max-age的優先級高於expires。
HttpOnly: 告訴瀏覽器不允許通過腳本document.cookie去更改值,這個值在document.cookie中也是不可見的,但是在http請求會攜帶這個cookie,
注意:這個值雖然在腳本中使不可取的,但是在瀏覽器安裝目錄中是以文件形式存在的,這個設置一般在服務器端設置的。
secure:安全標志,指定后,當secure為true時候,在HTTP中是無效的,在HTTPS中才有效,表示創建的cookie只能在HTTPS連接中被瀏覽器傳遞到服務器端進行會話驗證,如果是HTTP連接則不會傳遞該信息,所以一般不會被且聽到。
服務器端設置cookie代碼如下:
const express = require('express'); const app = express(); app.listen(3001, () => { console.log('port listen 3001'); }); app.get('/', (req, res) => { res.setHeader('status', '200 OK'); res.setHeader('Set-Cookie', 'isVisit=1;domain=/;path=/;max-age=60*1000'); res.send('歡迎你~'); });
直接設置Set-Cookie過於原始,我們可以對cookie的設置過程做如下封裝;
const express = require('express'); const app = express(); app.listen(3001, () => { console.log('port listen 3001'); }); const serilize = function(name, value, options) { if (!name) { throw new Errpr('cookie must have name'); } var rets = []; value = (value !== null && value !== undefined) ? value.toString() : ''; options = options || {}; rets.push(encodeURIComponent(name) + "=" +encodeURIComponent(value)); if (options.domain) { rets.push('domain=' + options.domain); } if (options.path) { rets.push('path=' + options.path); } if (options.expires) { rets.push('expires=' + options.expires.toGMTString()); } if (options.maxAge && typeof options.maxAge === 'number') { rets.push('max-age=' + options.maxAge); } if (options.httpOnly) { rets.push('HTTPOnly'); } if (options.secure) { rets.push('secure'); } return rets.join(';'); }; app.get('/', (req, res) => { res.setHeader('status', '200 OK'); res.setHeader('Set-Cookie', serilize('isVisit', '1')); res.send('歡迎你~'); });
1.2 服務器端解析cookie
cookie可以設置不同的域和路徑,所以對於同一個name value,在不同的域不同的路徑下是可以重復的,瀏覽器會按照與當前請求的url或頁面地址最佳匹配的順序來排定先后順序。
服務器端解析代碼如下:
const parse = function(str) { if (!str) { return; } const dec = decodeURIComponent; var cookies = {}; const rets = str.split(/\s*;\s*/g); rets.forEach((r) => { const pos = r.indexOf('='); const name = pos > -1 ? dec(r.substr(0, pos)) : r; const val = pos > -1 ? dec(r.substr(pos + 1)) : null; // 只需要拿到最匹配的那個 if (!cookies.hasOwnProperty(name)) { cookies[name] = val; } }); return cookies; };
因此整個代碼如下:
const express = require('express'); const app = express(); app.listen(3001, () => { console.log('port listen 3001'); }); const serilize = function(name, value, options) { if (!name) { throw new Errpr('cookie must have name'); } var rets = []; value = (value !== null && value !== undefined) ? value.toString() : ''; options = options || {}; rets.push(encodeURIComponent(name) + "=" +encodeURIComponent(value)); if (options.domain) { rets.push('domain=' + options.domain); } if (options.path) { rets.push('path=' + options.path); } if (options.expires) { rets.push('expires=' + options.expires.toGMTString()); } if (options.maxAge && typeof options.maxAge === 'number') { rets.push('max-age=' + options.maxAge); } if (options.httpOnly) { rets.push('HTTPOnly'); } if (options.secure) { rets.push('secure'); } return rets.join(';'); }; const parse = function(str) { if (!str) { return; } const dec = decodeURIComponent; var cookies = {}; const rets = str.split(/\s*;\s*/g); rets.forEach((r) => { const pos = r.indexOf('='); const name = pos > -1 ? dec(r.substr(0, pos)) : r; const val = pos > -1 ? dec(r.substr(pos + 1)) : null; // 只需要拿到最匹配的那個 if (!cookies.hasOwnProperty(name)) { cookies[name] = val; } }); return cookies; }; app.get('/', (req, res) => { res.setHeader('status', '200 OK'); res.setHeader('Set-Cookie', serilize('isVisit', '1')); res.send('歡迎你~'); console.log(parse('isVisit=1')); });
在命令行中運行后可以看到打印出cookie信息鍵值對,如下:
1.3 express中的cookie
express4中操作的cookie使用 cookie-parser模塊;如下代碼使用:
const express = require('express'); const cookieParser = require('cookie-parser'); const app = express(); app.listen(3000, () => { console.log('port listen 3000'); }); app.use(cookieParser()); app.get('/', (req, res) => { if (req.cookies.isVisit) { console.log(req.cookies); res.send('再次歡迎你'); } else { // cookie設置過期時間為10分鍾 res.cookie('isVisit', 1, {maxAge: 60*1000}); res.send('歡迎你~'); } });
二:session
cookie操作很方便,但是使用cookie安全性不高,cookie中的所有數據在客戶端就可以被修改,數據很容易被偽造;所以一些重要的數據就不能放在cookie當中了,並且cookie還有一個缺點就是不能存放太多的數據,為了解決這些問題,session就產生了,session中的數據保留在服務端的。
2.1 基於Cookie來實現用戶和數據的映射
把數據放到cookie中是不可取的,但是我們可以將口令放在cookie中的,比如cookie中常見的會放入一個sessionId,該sessionId會與服務器端之間會產生
映射關系,如果sessionId被篡改的話,那么它就不會與服務器端數據之間產生映射,因此安全性就更好,並且session的有效期一般比較短,一般都是設置是
20分鍾,如果在20分鍾內客戶端與服務端沒有產生交互,服務端就會將數據刪除。
session的原理是通過一個sessionid來進行的,sessionid是放在客戶端的cookie中,當請求到來時候,服務端會檢查cookie中保存的sessionid是否有,
並且與服務端的session data映射起來,進行數據的保存和修改,也就是說當我們瀏覽一個網頁時候,服務端會隨機生成一個1024比特長的字符串,然后存在
cookie中的sessionid字段中,當我們下次訪問時,cookie會帶有sessionid這個字段。
express 中 express-session模塊
在express中操作session可以使用 express-session這個模塊,主要方法是session(options),options中包含可選的參數有:
name: 保存session的字段名稱。默認為 connect.sid
store: session的存儲方式,默認存放在內存中。
secret: 通過設置secret字符串,來計算hash值並放在cookie中,使產生的signedCookie防篡改。
cookie: 設置存放session id的cookie的相關選項,默認為 (default: { path: '/', httpOnly: true, secure: false, maxAge: null })
genid: 產生一個新的 session_id 時,所使用的函數, 默認使用 uid2 這個 npm 包。
rolling: 每個請求都重新設置一個 cookie,默認為 false。
resave: 即使 session 沒有被修改,也保存 session 值,默認為 true
2.2 在內存中存儲session
如下代碼:
const express = require('express'); const session = require('express-session'); const app = express(); app.listen(3002, () => { console.log('port listen 3002'); }); app.use(session({ secret: 'somesecrettoken', cookie: { maxAge: 1*60*1000 } // 1分鍾 })); app.get('/', (req, res) => { /* 檢查session中的isVisit字段 */ if (req.session.isVisit) { res.send('再次歡迎你'); } else { req.session.isVisit = true; res.send('歡迎你第一次來~'); } });