express-12 Cookie與會話

簡介

  • HTTP是無狀態協議。當瀏覽器中加載頁面,而後轉到同一網站的另外一頁面時,服務器和瀏覽器都沒有任何內在的方法能夠認識到,這是同一瀏覽器訪問同一網站。換一種說法,Web工做的方式就是在每一個HTTP請求中都要包含全部必要的信息,服務器才能知足這個請求。前端

  • 因此須要用某種辦法在HTTP上創建狀態,因而便有了cookie和會話。git

關於cookie

  • cookie的想法很簡單:服務器發送一點信息,瀏覽器在一段可配置的時期內保存它。發送哪些信息確實是由服務器來決定:一般只是一個惟一ID號,標識特定瀏覽器,從而維持一個有狀態的假象。數據庫

  • cookie對用戶來講不是加密的;express

  • 服務器向客戶端發送的全部cookie都能被客戶端查看。npm

  • 用戶對cookie有絕對的控制權,能夠刪除或禁用cookiejson

  • 通常的cookie能夠被篡改bootstrap

  • 無論瀏覽器何時發起一個跟cookie關聯的請求,盲目地相信cookie中的內容,都有可能會受到攻擊。要確保cookie不被篡改,使用簽名cookie。瀏覽器

cookie能夠用於攻擊

  • 跨站腳本攻擊 (XSS)攻擊方式。XSS攻擊中有一種技術就涉及用惡意的JavaScript修改cookie中的內容。因此不要輕易相信返回到服務器的cookie內容。
  • 用簽名cookie會有幫助(不論是用戶修改的仍是惡意JavaScript修改的,這些篡改都會在簽名cookie中留下明顯的痕跡),而且還能夠設定選項指明cookie只能由服務器修改。這些cookie的用途會受限,但它們確定更安全。**安全

  • 若是能夠選擇,會話要優於cookie服務器

  • 大多數狀況下,能夠用會話維持狀態; 而且會話更容易,不用擔憂會濫用用戶的存儲,並且也更安全。

  • 當服務器但願客戶端保存一個cookie時,它會發送一個響應頭Set-Cookie,其中包含名稱/值對。當客戶端向服務器發送含有cookie的請求時,它會發送多個請求頭Cookie,其中包含這些cookie的值。**

憑證的外化

  • 爲了保證cookie的安全,必須有一個cookie祕鑰。cookie祕鑰是一個字符串,服務器知道它是什麼,它會在cookie發送到客戶端以前對cookie加密。這是一個不須要記住的密碼,因此能夠是隨機字符串。
  • 推薦用一個隨機密碼生成器來生成cookie祕鑰。**

  • 外化第三方憑證是一種常見的作法,好比cookie祕鑰、數據庫密碼和API令牌(Twitter、Facebook等)。這不只易於維護(容易找到和更新憑證),還可讓版本控制系統忽略這些憑證文件。這對放在GitHub或其餘開源源碼控制庫上的開源代碼庫尤爲重要。**

  • 所以能夠準備將憑證外化在一個JavaScript文件中(用JSON或XML也行,但我以爲JavaScript最容易)。

  • 建立文件credentials.js:

module.exports = {
    cookieSecret: '把你的cookie祕鑰放在這裏',
};
  • 如今,爲了防止不慎把這個文件添加到源碼庫中,在.gitignore文件中加上credentials.js。
var credentials = require('./credentials.js');

Express中的Cookie

在程序中開始設置和訪問cookie以前,須要先引入中間件cookie-parser。首先npm install --save cookie-parser,而後

app.use(require('cookie-parser')(credentials.cookieSecret));
  • 完成這個以後,就能夠在任何能訪問到響應對象的地方設置cookie或簽名cookie:
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');

設置cookie時可使用以下這些選項:

  • 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值會重置爲它的原始值。

會話

  • 會話實際上只是更方便的狀態維護方法。
  • 要實現會話,必須在客戶端存些東西,不然服務器沒法從一個請求到下一個請求中識別客戶端。
  • 一般的作法是用一個包含惟一標識的cookie,而後服務器用這個標識獲取相應的會話信息; cookie不是實現這個目的的惟一手段,好比在URL中添加會話信息等;但這些技術混亂、困難且效率低下,因此最好別用。
  • HTML5爲會話提供了另外一種選擇,那就是本地存儲,但如今尚未使人歎服的理由去採用這種技術而放棄通過驗證有效的cookie。

  • 從廣義上來講,有兩種實現會話的方法:
    • 把全部東西都存在cookie裏,被稱爲「基於cookie的會話」,而且僅僅表示比使用cookie便利;然而,它還意味着要把添加到cookie中的全部東西都存在客戶端瀏覽器中,因此不推薦
    • 只在cookie裏存一個惟一標識,其餘東西都存在服務器上。

內存存儲

  • 把會話信息存在服務器上,那麼必須找個地方存儲它。入門級的選擇是內存會話。
  • 它們很是容易設置,但也有個巨大的缺陷:重啓服務器後會話信息就消失了。更糟的是,若是擴展了多臺服務器,那麼每次請求多是由不一樣的服務器處理的,因此會話數據有時在那裏,有時不在。這明顯是不可接受的用戶體驗。
  • 然而出於開發和測試的須要,有它就足夠了。

  • 首先安裝express-session(npm install --save express-session)

  • 而後,在鏈入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設置 (pathdomainsecure等)。適用於常規的cookie默認值。

使用會話

  • 會話設置好之後,使用起來就再簡單不過了,只是使用請求對象的session變量的屬性:
req.session.userName = 'Anonymous';
var colorScheme = req.session.colorScheme || 'dark';
  • 注意,對於會話而言,咱們不是用請求對象獲取值,用響應對象設置值,它全都是在請求對象上操做的。(響應對象沒有session屬性。)要刪除會話,能夠用JavaScript的delete操做符:
req.session.userName = null;        // 這會將'userName'設爲null;但不會移除它
delete req.session.colorScheme;   // 這會移除'colorScheme'

用會話實現即顯消息

  • 「即顯」消息(不要跟Adobe Flash搞混了)只是在不破壞用戶導航的前提下向用戶提供反饋的一種辦法。
  • 用會話實現即顯消息是最簡單的方式(也能夠用查詢字符串,但那樣除了URL會更醜外,還會把即顯消息放到書籤裏)
//使用了bootstrap
{{#if flash}}
  <div class="alert alert-dismissible alert-{{flash.type}}"> 
    <button type="button" class="close"
      data-dismiss="alert" aria-hidden="true">&times;</button>
    <strong>{{flash.intro}}</strong> {{{flash.message}}}
  </div>
{{/if}}
  • 注意,在flash.message外面用了3個大括號,這樣咱們就能夠在消息中使用簡單的HTML(多是要加劇單詞或包含超連接)。
  • 接下來添加一些中間件,若是會話中有flash對象,將它添加到上下文中。即顯消息顯示過一次以後,咱們就要從會話中去掉它,以避免它在下一次請求時再次顯示。在路由以前添加下面這段代碼:**
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

會話的用途

  • 當想跨頁保存用戶的偏好時,能夠用會話。會話最多見的用法是提供用戶驗證信息,在登陸後就會建立一個會話。以後就不用在每次從新加載頁面時再登陸一次。
  • 即使沒有用戶帳號,會話也有用。網站通常都要記住你喜歡如何排列東西,或者你喜歡哪一種日期格式,這些都不須要登陸。
  • 儘管我建議優先選擇會話而不是cookie,但理解cookie的工做機制也很重要(特別是由於有cookie才能用會話)。它對於你在應用中診斷問題、理解安全性及隱私問題都有幫助。 
相關文章
相關標籤/搜索