nodeJS之Cookie和Session(一)

龍恩0707發表於2018-05-27

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

相關文章