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('歡迎你第一次來~'); } });