Node.js+Express 開發之Cookie、Session 使用詳解

雨林星空發表於2019-11-26

  為什麼有cookie 和 session ?

  因為HTTP協議是沒有狀態的,當使用者再次訪問網站時,沒法判斷之前是否登陸過,於是就有了cookies和session,用來儲存使用者的一些資訊。

  cookie 和 session 區別?

  cookie 是存放在客戶端瀏覽器的,每個域名下通常限制為50個cookie,每個cookie 的值大小限制為4K。session 是存放在伺服器端的,可以儲存無限大的資料,但大量的session,會佔用較多的伺服器記憶體。

  cookie 只能存放字串型別資料,session可以存放任意型別資料。

  cookie 是儲存在瀏覽器客戶端的,所以也很容易被提取,安全方面存在隱患。session 儲存在伺服器端相對安全。

  cookie的工作原理

  當使用者登入網站成功,伺服器會在返回響應資料的同時,將使用者的cookie資訊也下發到客戶端,之後客戶端每次發起請求會攜帶著這個cookie,從而免去網站登入的步驟。

  session的工作原理

  使用者的session資料儲存在伺服器記憶體中,伺服器只下發一個session標識(session_id),它是一個唯一的隨機字串,被儲存到cookie中,下次瀏覽器的請求攜帶著包含session_id的cookie,然後伺服器在記憶體中查詢該session_id 是否有對應的資料,如果有則是已登入使用者。

  看起來,session 技術相當於Cookie技術的升級,session是基於cookie的,但是cookie只是起到了 session_id 載體的作用,而url的查詢引數,http請求頭裡的欄位都可以起到session_id 載體的作用,所以沒有cookie也可以實現session。另外,使用者的session資料不僅可以存放在伺服器記憶體中,也可以存放到檔案或資料庫中,要持久化session 同時也要持久化cookie的session_id。

  nodejs+express 開發中 cookie 的使用

  node.js 的web框架 express 在4.x版本後,許多模組不再包含在其中,而是需要單獨下載安裝。

  cookie 常用模組是 cookie-parser,安裝命令如下:

npm install cookie-parser

  基本用法

  程式碼示例:

let express = require("express");
let cookieParser = require("cookie-parser"); // 引用cookie-parser

let app = express();
app.use(cookieParser()); // 使用cookie中介軟體cookie-parser


// 首頁,讀取cookie
app.get("/", function (req, res) {
    console.log("name: " + req.cookies.name, "profile: " + JSON.stringify(req.cookies.profile));
    res.send("name: " + req.cookies.name + "<br/>" + "profile: " + JSON.stringify(req.cookies.profile));
});

// 登入,註冊cookie
app.get("/login", function (req, res) {

    // 定義cookie引數物件
    let cookieOptions = {
        httpOnly: true,
        maxAge: 10 * 60 * 1000, // 10分鐘後cookie失效
    }

    // 建立cookie,值為字串
    res.cookie("name", "xiaoyu", cookieOptions);

    // 建立cookie,值可以寫成json物件形式 {....} 、[...]
    res.cookie('profile', {
        age: '25',
        height: '160cm',
        weight: '50kg'
    }, cookieOptions);

    res.send("已註冊cookie");
});


// 退出,清除cookie
app.get("/exit", function (req, res) {
    res.clearCookie("name");
    res.clearCookie("profile");
    res.send("已清除cookie");
});

app.listen(3000);

  使用簽名cookie

  使用簽名 cookie,起防篡改功能。

  程式碼示例:

let express = require("express");
let cookieParser = require("cookie-parser"); // 引用cookie-parser

let app = express();
app.use(cookieParser("1234567abcdefg")); // 使用cookie-parser,傳入簽名防篡改,可自定義一個複雜的字串


// 首頁,讀取cookie
app.get("/", function (req, res) {
    console.log("name: " + req.signedCookies.name, "profile: " + JSON.stringify(req.signedCookies.profile));
    res.send("name: " + req.signedCookies.name + "<br/>" + "profile: " + JSON.stringify(req.signedCookies.profile));
});

// 登入,註冊cookie
app.get("/login", function (req, res) {

    // 定義cookie引數物件
    let cookieOptions = {
        httpOnly: true,
        maxAge: 10 * 60 * 1000, // 10分鐘後cookie失效
        signed: true // 使用簽名
    }

    // 建立cookie,值為字串
    res.cookie("name", "xiaoyu", cookieOptions);

    // 建立cookie,值可以寫成json物件形式 {....} 、[...]
    res.cookie('profile', {
        age: '25',
        height: '160cm',
        weight: '50kg'
    }, cookieOptions);

    res.send("已註冊cookie");
});


// 退出,清除cookie
app.get("/exit", function (req, res) {
    res.clearCookie("name");
    res.clearCookie("profile");
    res.send("已清除cookie");
});

app.listen(3000);

   cookie 引數選項說明:

   domain:表示cookie在什麼域名下有效,預設為當前網站域名。如果跨域訪問,A站域名為 A.test.com,B站域名為 B.test.com,那麼在域A生產一個令域A和域B都能訪問的cookie就要將該cookie的domain設定為: ".test.com"

  expires:cookie過期時間,型別為Date。例如:expires: new Date(Date.now() + 10*60*1000)  表示10分鐘後cookie過期。如果沒有設定或者設定為0,那麼該cookie 在關閉瀏覽器後失效。expires早在http/1.0中已經定義,max-age在http/1.1中定義, 現在基本都是http/1.1,expires已經被max-age屬性所取代,建議優先使用maxAge

  maxAge:和expires類似,設定cookie過期的時間,指明從現在開始,多少毫秒以後cookie到期。maxAge:10*60*1000  表示10分鐘後Cookie過期。 完全不寫 maxAge 項,只要關閉瀏覽器cookie就失效。maxAge:0  表示從客戶端刪除此cookie。  

  httpOnly:禁止JS指令碼document.cookie 讀取cookie內容,防止XSS攻擊產生. 預設為false。建議設定為true

  path:cookie在什麼路徑下有效,預設為'/'

  secure:當secure值為true時,cookie在HTTP中是無效,只在HTTPS中才有效。預設為false

  signed:使用簽名,起到防篡改功能。預設為false。  此項並不是加密,cookie值仍然被明文顯示出來,對於cookie的加密可自己使用md5、sha1、hmac、aes等方法,或使用中介軟體cookie-encrypter
 

  對 cookie 加密

  這裡使用第三方模組 cookie-encrypter 對cookie 進行加密。該模組預設採用的是AES256 對稱加密演算法

     安裝cookie-encrypter

npm install cookie-encrypter

  程式碼示例

let express = require("express");
let cookieParser = require("cookie-parser"); 
let cookieEncrypter = require("cookie-encrypter"); // 引用cookie-encrypter 模組

let app = express();
const secretKey = 'foobarbaz1234567foobarbaz1234567'; // 預設cookie-encrypter模組使用的是 AES256 加密演算法,須輸入32個字元
app.use(cookieParser(secretKey)); 
app.use(cookieEncrypter(secretKey)); // 傳入金鑰,加密cookie,主要是這句

app.get("/", function (req, res) {    
    console.log("name: " + req.signedCookies.name, "profile: " + JSON.stringify(req.signedCookies.profile));
    res.send("name: " + req.signedCookies.name + "<br/>" + "profile: " + JSON.stringify(req.signedCookies.profile));
});

app.get("/login", function (req, res) {
    let cookieOptions = {
        httpOnly: true,
        signed: true,
        maxAge: 10 * 60 * 1000,
        // plain: true  //  該選項是cookie-encrypter模組的選項。等於true時,表示不再使用cookie-encrypter進行加密。
    };
    res.cookie('name', 'xiaoyu', cookieOptions);
    res.cookie('profile', {
        age: '25',
        height: '160cm',
        weight: '50kg'
    }, cookieOptions);
    res.send("已註冊cookie");
});

app.get("/exit", function (req, res) {
    res.clearCookie('name');
    res.clearCookie('profile');
    res.send("已清除cookie");
});

app.listen(3000);

  nodejs+express 開發中 session 的使用

  處理session常用的中介軟體之一是 express-session

  安裝 express-session 模組,命令如下:

  npm install express-session

  程式碼示例

let express = require("express");
let session = require("express-session");
let app = express();

app.use(session({
    secret: "nodejs world", 
    resave: false, 
    saveUninitialized: false, 
    name: "session_id", 
    rolling: true, 
    cookie: {
        path: '/',
        httpOnly: true,
        secure: false,
        maxAge: 30 * 60 * 1000 // 設定session_id的cookie有效時間,也是後臺session的有效時長。(預設為值為null,當關閉瀏覽器時,session_id cookie失效)。
    }
}));


app.get("/", function (req, res) {
    if (req.session.login) {
        res.send("歡迎您" + req.session.userName + ", " + req.sessionID);
    } else {
        res.send("你沒有登入!");
    }

});

app.get("/login", function (req, res) {
    req.session.login = true;
    req.session.userName = "xiaoyu";
    res.send("您已經成功登入");
});


app.get("/exit", function (req, res) {
    req.session.destroy();
    res.send("您已經退出");
});


app.listen(3000);

  session 配置選項說明

  secret: 對session_id cookie的簽名金鑰。

  resave: 強制儲存 session 即使它並沒有變化。預設為true,建議設定成false

  saveUninitialized: 強制將未初始化的session儲存。預設為 true。 設定為true 不管用不用到session都會初始化。設定為false 用到session時才會去初始化。

  name: 這裡設定session_id 的cookie的名字。預設名為 'connect.sid'

  rolling: 在每次請求時強行設定cookie,這將重置session-id 的 cookie過期時間。預設:false

  cookie:  session_id 的cookie 選項。預設值是 { path: '/', httpOnly: true, secure: false, maxAge: null }

 

  將session 存放在 mongoDB 資料庫中

  session 預設存放在記憶體中,也可以存放到 redis,mongodb 等資料庫中。express 生態中都有相應模組支援。

  將session儲存到mongodb 資料庫,可以安裝connect-mongo模組支援

npm install connect-mongo

  程式碼示例

let express = require("express");
let session = require("express-session");
let MongoStore = require('connect-mongo')(session);  // 引用 connect-mongo

let app = express();

app.use(session({
    secret: "nodejs world",
    resave: false,
    saveUninitialized: false, 
    name: "session_id",
    rolling: true, 
    cookie: {
        path: '/',
        httpOnly: true,
        secure: false, 
        maxAge: 30 * 60 * 1000  // session_id cookie 和 session的有效時長
    },
    store: new MongoStore({
     url: 'mongodb://localhost:27017/mydb', // 指定mongoDB 主機地址、埠、資料庫
     ttl: 30*60 // 設定session過期時間,時間到自動移除。單位秒。一般設定為30分鐘即可。未設定cookie:maxAge時有效。
    })
}));


app.get("/", function (req, res) {
    if (req.session.login) {
        res.send("歡迎您" + req.session.userName + ", " + req.sessionID);
    } else {
        res.send("你沒有登入!");
    }
});

app.get("/login", function (req, res) {
    req.session.login = true;
    req.session.userName = "xiaoyu";
    res.send("您已經成功登入");
});


app.get("/exit", function (req, res) {
    req.session.destroy();
    res.send("您已經退出");
});


app.listen(3000);

  將session 存放在 redis 資料庫中  

  將session儲存到redis 資料庫,可以安裝redis-mongo模組支援。事先需安裝好redis的nodejs客戶端

npm install redis
npm install connect-redis

  程式碼示例

let express = require("express");
let session = require("express-session");
let redis = require("redis"); // 引用redis客戶端
let RedisStore = require('connect-redis')(session); // 引用connect-redis
let app = express();

let redisClient = redis.createClient({ // 設定redis 主機地址、埠、資料庫、密碼
    host: "localhost",
    port: "6379",
    //password: "123456",
    db: 1
});

app.use(session({
    secret: "nodejs world",
    resave: false,
    saveUninitialized: false,  
    name: "session_id", 
    rolling: true, 
    cookie: {
        path: '/',
        httpOnly: true,
        secure: false, 
        maxAge: 30 * 60 * 1000 // session_id cookie 和 session的有效時長
    },
    store: new RedisStore({
        client: redisClient, // 指定redis客戶端
         ttl: 30 * 60, // 設定session過期時間,時間到自動移除。單位秒。一般設定為30分鐘即可。未設定cookie:maxAge時有效。      
         logErrors: true, // 是否列印redis出錯資訊,預設false
        prefix: "sess:" // key字首,預設為 "sess:"
    })
    
}));


app.get("/", function (req, res) {
    if (req.session.login) {
        res.send("歡迎您" + req.session.userName + ", " + req.sessionID);
    } else {
        res.send("你沒有登入!");
    }

});

app.get("/login", function (req, res) {
    req.session.login = true;
    req.session.userName = "xiaoyu";
    res.send("您已經成功登入");
});


app.get("/exit", function (req, res) {
    req.session.destroy();
    res.send("您已經退出");
});

app.listen(3000);

 

相關文章