「翻譯」express-session 中介軟體

greenlihui發表於2019-04-05

前言:最近在使用 express-session 中介軟體,檢視使用的時候有些引數不是很清楚就花了一點時間把文件翻譯了一下。

其中常用設定中令人迷糊的是 resave 和 saveUnintialized 屬性,關於這兩個屬性,引用來自 CNODE 社群更通熟易懂的解釋

resave : 是指每次請求都重新設定 session cookie,假設你的 cookie 是 10 分鐘過期,每次請求都會再設定 10 分鐘。
saveUninitialized: 是指無論有沒有 session cookie ,每次請求都設定個 session cookie ,預設給個標示為 connect.sid。

以下為正文

Installation

這是一個通過 npm registry 可用的 Node.js 模組。使用以下 npm install 命令來完成安裝。

$ npm install express-session
複製程式碼

API

var session = require('express-session');
複製程式碼

sessions(options)

使用給定選項建立一個 session 中介軟體。

注意:只有 session ID 是儲存在 cookie 中,Session 資料本身並不是。Session 資料是存在服務端。

注意:從版本 1.5.0 起,本模組不再需要 cookie-parser 中介軟體來執行。本模組現在直接在 req/res 上讀寫 cookies。當本模組和 cookie-parser 的 secret 不一致時,使用 cookie-parser 可能會導致問題。

警告:預設的服務端 session 儲存,MemoryStore,特意沒有為生產環境而設計。在大多數情況下,它可能會導致記憶體洩漏,不會擴充套件超過單個程式,本是用於除錯和開發。

對於儲存列表,請檢視相容的 session 儲存

Options

express-session 在 options 物件中接收以下引數

cookie

session ID cookie 的設定物件。預設值為 { path: '/', httpOnly: true, secure: false, maxAge: null }.

下列引數可選設定放入 cookie 物件。

cookie.domain

為 Set-Cookie 屬性指定 domain。預設情況下,沒有設定 domain,並且大多數客戶端會將 cookie 視為僅應用於當前 domain。

cookie.expires

為 Set-Cookie 屬性中的 Expires 指定 Date 物件。預設情況下,沒有設定 expires,大多數客戶端會將視這個為 "非持久化 cookie" 並且在像退出瀏覽器應用的場景下刪除該 cookie。

注意:如果 options 物件中同時設定了 expires 和 maxAge,那麼將被用到的是在物件中最後一個被定義的屬性。

注意:expires 選項不應該被直接設定;而應該只使用 maxAge 選項。

cookie.httpOnly

為 Set-Cookie 屬性中的 HttpOnly 指定 boolean 值。當為真值,HttpOnly 屬性被設定,否則不被設定。預設情況下,HttpOnly 屬性是被設定的。

注意:設定該值為 true 的時候要小心,因為服從協議的客戶端不會允許 JavaScript 在 document.cookie 中檢視 cookie。

cookie.maxAge

指定當計算 Set-Cookie 屬性中的 Expires 時使用的 number (毫秒)值。這是通過獲取當前伺服器時間並將 maxAge 毫秒數加入其中計算 Expires 日期時間來完成的。預設情況下沒有設定 maxAge。

注意:如果 options 物件中同時設定了 expires 和 maxAge,那麼將被用到的是在物件中最後一個被定義的屬性。

cookie.path

為 Set-Cookie 屬性指定 Path 值。預設情況下該值被設為 '/',也就是 domain 下的根路徑。

cookie.sameSite

為 Set-Cookie 屬性中的 SameSite 指定 boolean 或者 string 值。其中,

  • true 會將 SameSite 屬性設為 Strict 以實現嚴格的相同站點強制。
  • false 不會 SameSite 屬性。
  • 'lax' 會將 SameSite 屬性設定為 Lax 以實現寬鬆的相同站點強制。
  • 'strict' 會將 SameSite 屬性設定為 Strict 以實現嚴格的相同站點強制。

關於不同的強制級別的更多資訊可以在細則中找到tools.ietf.org/html/draft-…

注意:這是一個還未被完全標準化的屬性並且將來可能發生變化。這意味著許多客戶端可能忽略這條屬性直到它們完全理解它為止。

cookie.secure

為 Set-Cookie 屬性中的 Secure 指定 boolean 值。當為真時,Secure 屬性被設定否則沒有設定。預設情況下 Secure 屬性沒有被設定。

注意:當設定該值為 true 的時候請小心,因為如果瀏覽器沒有建立 HTTPS 連線服從協議的客戶端將不會傳送 cookie 返回給服務端。

請注意 secure: true 是推薦選項。然而,它需要啟用 HTTPS 的網站, 也就是 HTTPS 是 secure cookies 所必須的。如果 secure 被設定而你通過 HTTP 訪問你的站點,cookie 將不會被設定。如果你在代理後使用 node.js 並且設定 secure: true,你需要在 express 中設定 "trust proxy":

var app = express()
app.set('trust proxy', 1) // trust first proxy
app.use(session({
  secret: 'keyboard cat',
  resave: false,
  saveUninitialized: true,
  cookie: { secure: true }
}))
複製程式碼

為了在生產環境中使用 secure cookies,同時允許在開發環境中測試,下列是在 express 中基於 NODE_ENV 啟用此設定的示例:

var app = express()
var sess = {
  secret: 'keyboard cat',
  cookie: {}
}
 
if (app.get('env') === 'production') {
  app.set('trust proxy', 1) // trust first proxy
  sess.cookie.secure = true // serve secure cookies
}
 
app.use(session(sess))
複製程式碼

cookie.secure 選項也可以被設定成特殊值 "auto" 來讓這個設定自動和確定的連線的安全性相匹配。如果站點可以同時用做 HTTP 和 HTTPS 請小心使用這個設定,因為一旦 cookie 的 HTTPS 屬性被設定,cookie 不會再對 HTTP 可見。當 Express 的 "trust proxy" 被正確設定來簡化開發和生產配置的時候,這非常有用。

genid

呼叫來生成一個新的 session ID 的函式。提供一個返回 string 型別並將被用來作為 session ID 的函式。當生成 ID 的時候如果你想用一些附加到 req 的值,該函式已給定 req 作為第一個引數。

預設值是一個使用 uid-safe 庫來生成 ID 的函式。

注意:請小心生成唯一的 ID 以便你的 sessions 不會產生衝突。

app.use(session({
  genid: function(req) {
    return genuuid() // use UUIDs for session IDs
  },
  secret: 'keyboard cat'
}))
複製程式碼
name

設定在 response 中(和從 request 中讀取)的 session ID 的 cookie 的 name。

預設值為 "connect.sid"

注意:如果你有多個執行在相同 hostname(只是名字,也就是 localhost 或者 127.0.0.1;不同的協議(scheme) 和 埠(port) 不命名不同的主機名)上的應用,那麼你需要將 session cookie 彼此分開。最簡單的方法是每個應用設定不同的 name。

proxy

當設定 secure cookies 的時候相信反向代理(通過 "X-Forwarded-Proto" 頭)。

預設值為 undefined

  • true 表示 "X-Forwarded-Proto" 頭將會被使用。
  • false 表示只有存在直接的 TLS/SSL 連線時才會忽略所有頭並認為連線是安全的。
  • undefined 表示從 express 中使用 "trust proxy"。
resave

即使 session 在請求期間從未被修改過也強制 session 儲存回 session 儲存(store)。根據你的儲存這可能是必須的,但是這也可能創造競爭條件當客戶端傳送兩個並行請求到你的服務端並且其中一個請求 A 對 session 作出的更改可能會在另一個請求 B 結束時被覆蓋即使請求 B 沒有做任何更改(這個行為取決於你用的 session 儲存)。

預設值為 true,但是不推薦使用預設值,因為預設值將來會被更改。請研究此項設定並選擇適合你的用例的選項。一般來講,你會想選擇 false。

該如何知道該設定對我的 session 儲存來講是不是必須的呢?最好的方法是檢查你的儲存看它是否實現了 touch 方法。如果它實現了,那你可以安全地設定 resave 為 false。如果它沒有實現 touch 方法而且你的 store 在儲存的 sessions 中設定的 expiration 日期,那麼你可能需要設定 resave: false

rolling

強制在每次響應的時候設定一個 session 標誌符 cookie。expiration 重新被設定為初始的 maxAge,重置 expiration 倒數計時。

預設值為 false。

注意:當該選項被設定為 true 但是 saveUninitialized 選項被設定為 false,則不會在具有未初始化的 session 響應中設定 cookie。

saveUninitialized

強制將未初始化的 session 儲存回 store。當一個 session 是新的但是還未被修改時我們說他是未初始化的。選擇 false 值對實現登入 session 是有用的,因為它減少了伺服器儲存的用量,遵守了設定 cookie 前需要許可的規則。選擇 false 值也有助於客戶端在沒有回話的情況下發出過個並行請求的競爭條件。

預設值為 true,但是不推薦使用預設值,因為預設值將來會被更改。請研究此項設定並選擇適合你的用例的選項。

注意:如果你正在結合 PassportJS 使用 Session,使用者通過身份驗證後PassportJS 將為該使用者在 session 中新增一個空的 Passport 物件,這將會視為對 session 的修改,導致 session 被儲存。這已經在 PassportJS 0.3.0 中被修復。

secret

必設選項

這是用來給 session ID cookie 簽名的 secret。這可以是單個 secret 的字串也可以是多個 secret 組成的陣列。如果提供了一組 secrets,只有第一個元素會被用來給 session ID cookie 簽名,在驗證請求籤名的時候才會考慮到所有元素。

store

session 儲存例項,預設為一個新的 MemoryStore 例項。

unset

控制取消設定 req.session 的結果(通過刪除,設為 null,等等)。

預設值為 'keep'

  • 'destroy' 表示當響應結束的時候 session 將會被銷燬(刪除)。
  • 'keep' 表示在 store 中的 session 會被保留,但是在請求期間做的修改將會被忽略不會被儲存。

req.session

儲存或者訪問 session 資料,只需要使用請求屬性 req.session,該屬性(通常)由 store 序列化為 session,所以一般來說巢狀物件也可以接受。下面的示例是一個基於特定使用者的檢視計數器:

// Use the session middleware
app.use(session({ secret: 'keyboard cat', cookie: { maxAge: 60000 }}))
 
// Access the session as req.session
app.get('/', function(req, res, next) {
  if (req.session.views) {
    req.session.views++
    res.setHeader('Content-Type', 'text/html')
    res.write('<p>views: ' + req.session.views + '</p>')
    res.write('<p>expires in: ' + (req.session.cookie.maxAge / 1000) + 's</p>')
    res.end()
  } else {
    req.session.views = 1
    res.end('welcome to the session demo. refresh!')
  }
})
複製程式碼

Session.regenerate(callback)

要重新生成 session 只需要呼叫這個方法。完成後一個新的 SID 和 Session 例項將會被初始化在 req.session 並且 callback 會被呼叫。

req.session.regenerate(function(err) {
  // will have a new session here
})
複製程式碼

Session.destroy(callback)

銷燬 Session 並取消設定 req.session 屬性。完成後將呼叫 callback。

req.session.destroy(function(err) {
  // cannot access session here
})
複製程式碼

Session.reload(callback)

從 store 重新載入 session 資料並重新填充 req.session 物件。完成後將呼叫 callback。

req.session.reload(function(err) {
  // session updated
})
複製程式碼

Session.save(callback)

將 session 儲存回 store,用記憶體中的內容替換 store 中的內容(儘管 store 可能還會做其他的事情—參閱 store 的文件以瞭解其確切的行為)。

如果 session 資料被改變了這個方法會在 HTTP 響應的末尾自動被呼叫(儘管這個行為可以被中介軟體構造器中的多種選項所改變)。因此,一般來講這個方法不需要被手動呼叫。

存在一些呼叫這個方法會很有用的情況,比如重定向,長期請求(long-lived requests)或著 WebSockets。

req.session.save(function(err) {
  // session saved
})
複製程式碼

Session.touch(callback)

更新 .maxAge 屬性。一般來講這個方法不需要被呼叫因為 session 中介軟體為你執行了這個操作。

req.session.id

每一個 session 都有一個與之關聯的唯一 ID。該屬性是 req.sessionID 的別名而且無法修改。該屬性已被新增以使 session ID 可以從 session 物件中訪問。

req.session.cookie

每一個 session 都有一個唯一的 cookie 與之伴隨。這允許你更改每個訪問者的 session cookie。例如我們可以設定 req.session.cookie.expires 為 false 來使 cookie 僅在使用者-代理的持續時間中保留。

Cookie.maxAge

req.session.cookie.maxAge 將以毫秒數返回剩餘的時間,我們也可以重新分配一個新值來適當地調整 .expires 屬性。以下程式碼是等效的:

var hour = 3600000
req.session.cookie.expires = new Date(Date.now() + hour)
req.session.cookie.maxAge = hour
複製程式碼

例如當 maxAge 被設定為 60000(一分鐘)時,三十秒後它將返回 30000 知道當前的請求已完成,此時呼叫 req.session.touch() 將會重設 req.session.maxAge 為它的初始值。

req.session.cookie.maxAge // => 30000
複製程式碼

req.sessionID

要拿到載入的 session 的 ID,訪問請求的屬性 req.sessionID。當 session 被載入或被建立的時候這僅是一個只讀的值。

Session Store Implementation

每一個 session store 必須是一個 EventEmitter 並且實現特定的方法。下列的方法是必需,推薦和可選的列表。

  • 必需的方法是此模組將會始終在 store 中呼叫的方法。
  • 推薦的方法是如果可用此模組將會在 store 中呼叫的方法。
  • 可選的方法是此模組根本不會呼叫的方法

有關示例實現請檢視 connect-redis 倉庫。

store.all(callback)

可選

該可選方法用於以陣列形式獲取 store 中的所有 session。回撥方法應該使用為 callback(error, sessions)

store.destroy(sid, callback)

必需

該必需方法根據給定的 session ID 來銷燬(刪除)store 中的 session。session被刪除後回撥函式應該使用為 callback(error)

store.clear(callback)

可選

該可選方法用於刪除 store 中的所有 session。store 清空後回撥函式應該使用為 callback(error)

store.length(callback)

可選

該可選方法用於獲取 store 中所有 session 的個數。回撥函式應該使用為 callback(error, len)

store.get(sid, callback)

必需

該必需方法根據給定的 session ID 從 store 中獲取 session。回撥函式應該使用為 callback(error, session)

如果找到 session 回撥函式中的 session 引數應該為一個 session 物件,否則如果沒有找到 session(並且也沒有錯誤)應該為 null 或 undefined。當 error.code === 'ENOENT' 表現為 callback(null, null) ,這是一種特殊情況。

store.set(sid, session, callback)

必需

該必需方法根據給定的 session ID 和 session 物件將 session 存入 store。session 存入 store 後回撥函式應該使用為 callback(error)

store.touch(sid, session, callback)

推薦

該推薦方法根據給定的 session ID 和 session 物件 "觸碰" 給定的 session 物件。session 被 "觸碰" 後回撥函式應該使用為 callback(error)

該方法主要用於 store 自動刪除空閒 session,並將此方法用於向 store 傳送給定 session 處於活動狀態的訊號,可能回重置空閒計時器。

Compatible Session Stores

下列的模組是實現了一個和本模組相容的 session store。請提出 PULL REQUEST 來新增其他的模組 :)

本處僅列出兩處 store 實現,更多請檢視原文件

connect-db2: 一個使用 ibm_db 模組建成的基於 IBM DB2 的 session store。

connect-mongo: 一個基於 SQL Server 的 session store。

Example

一個簡單使用 express-session 來為使用者儲存頁面訪問的例子:

var express = require('express')
var parseurl = require('parseurl')
var session = require('express-session')
 
var app = express()
 
app.use(session({
  secret: 'keyboard cat',
  resave: false,
  saveUninitialized: true
}))
 
app.use(function (req, res, next) {
  if (!req.session.views) {
    req.session.views = {}
  }
 
  // get the url pathname
  var pathname = parseurl(req).pathname
 
  // count the views
  req.session.views[pathname] = (req.session.views[pathname] || 0) + 1
 
  next()
})
 
app.get('/foo', function (req, res, next) {
  res.send('you viewed this page ' + req.session.views['/foo'] + ' times')
})
 
app.get('/bar', function (req, res, next) {
  res.send('you viewed this page ' + req.session.views['/bar'] + ' times')
})
複製程式碼

License

MIT

Keywords

none

相關文章