靜態網站很容易擴充。你只需要全部緩存,不需要考慮從不同伺服器組合有狀態的內容給用戶。
可惜,大多數的Web應用程式使用有狀態內容提供個人化體驗。如果你的應用程式可以登錄,就需要記住用戶的Session。經典的處理方法是客戶端設定包含隨機唯一Session標識的Cookie,被識別的Session資料保存到服務端。
擴充有狀態服務
當擴充服務的時候,你一定有三種選擇:
但都有缺陷:
然而,如果你換個角度思考,你會發現第四個選擇:將Session資料保存在客戶端
客戶端Session
在客戶端保存Session有一些優點:
但是客戶端Session有一個嚴重問題:你不能保證使用者不會竄改Session資料。
例如你在Cookie中儲存用戶的ID。用戶很容易修改它,從而訪問別人的帳戶。
這似乎否定了客戶端Session的可能,但有一種方法可以巧妙解決這問題:加密打包Session資料(還是存在Cookie中)。這樣就不需要擔心用戶修改Session數據,服務端會驗證數據的。
實際應用上,就是Cookie中保存一個加密的Server Key。 Server Key驗證後才有權利讀取和修改Session資料。這就是客戶端Session。
Node客戶端Session
Node.JS有一個函式庫可以實作客戶端Session:node-client-session.它可以取代Connect(一個Node中間件框架)內建的session和cookieParser中介軟體。
在Express框架應用程式中的使用:
const clientSessions = require("client-sessions");
app.use(clientSessions({ secret: '0GBlJZ9EKBt2Zbi2flRPvztczCewBxXK' // 设置一个随机长字符串! })
然後,為req.session物件新增屬性:
app.get('/login', function (req, res){ req.session.username = 'JohnDoe'; });
讀取屬性:
app.get('/', function (req, res){ res.send('Welcome ' + req.session.username); });
使用reset方法終止Session:
app.get('/logout', function (req, res) { req.session.reset(); });
即時註銷Persona Session
(註:Persona是Mozzilla推出的網路身分系統)
與伺服器端Session不同,客戶端Session的問題是服務端無法刪除Session。
伺服器端架構時,你可以刪除Session資料。任意的客戶端Cookie標識的Session很可能不存在。但客戶端架構時,Session資料不在服務端,無法保證Session資料在每個客戶端都被刪除。換句話說,我們無法同步使用者的客戶端狀態(已經登入)和服務端狀態(登出登入)。
為了彌補這個缺陷,客戶端Session中加入了過期時間。展開Session資料(被加密打包)前驗證過期時間。如果過期了,拋棄Session資料並改變使用者狀態(如登出登入)。
過期機制在許多應用中運作良好(尤其是短過期時間需求)。如在Persona中,當使用者發覺密碼收到威脅或已經損壞時,我們需要提供方法讓使用者立即登出Session資料。
這表示需要保留一點點狀態資訊在服務後端。我們處理即時登出的方法是新增一個Token在使用者資料表和Session資料中。
每次API呼叫時比對Session資料中的Token和資料庫中的Token。如果不相同,返回錯誤訊息並退出使用者。
這樣會附加多餘的資料庫操作去查詢Token。幸好,大多數的API呼叫都需要讀取使用者資料表,把Token一起帶上就好了。