NodeJS 使用 cookie 和 session

在這裏插入圖片描述

原文出自:https://www.pandashen.com

前言

由於瀏覽器無狀態的特性,cookie 技術應運而生,cookie 是一個會話級的存儲,大小 4KB 左右,用於瀏覽器將服務器設置的信息重新帶給服務器進行驗證,不支持跨域,在瀏覽器清空緩存或超過有效期後失效,不能存放敏感信息,session 是專門用於存儲最初設置給瀏覽器 cookie 數據的地方,我們本篇就來討論一下 cookiesession 在 NodeJS 中的使用方式。

cookie 的基本使用

1、NodeJS 原生操作 cookie

下面是 cookie 在 Node 原生中的讀取和寫入方法。

// 原生中操作 cookie
const http = require("http");

// 創建服務
http.createServer((req, res) => {
    if (req.url === "/read") {
        // 讀取 cookie
        console.log(req.headers.cookie);
        res.end(req.headers.cookie);
    } else if (req.url === "/write") {
        // 設置 cookie
        res.setHeader("Set-Cookie", [
            "name=panda; domain=panda.com; path=/write; httpOnly=true",
            `age=28; Expires=${new Date(Date.now() + 1000 * 10).toGMTString()}`,
            `address=${encodeURIComponent("回龍觀")}; max-age=10`
        ]);
        res.end("Write ok");
    } else {
        res.end("Not Found");
    }
}).listen(3000);

上面代碼創建了一個 http 服務器,可以通過讀取 cookie 請求頭的值來獲取瀏覽器發來的 cookie,服務器可以通過給瀏覽器設置響應頭 Set-Cookie 實現對瀏覽器 cookie 的設置,多個 cookie 參數爲數組,在數組內可以規定每一條 cookie 的規則,中間使用一個分號和一個空格隔開。

  • domain 用來設置允許訪問 cookie 的域;
  • path 用來設置允許訪問 cookie 的路徑;
  • httpOnly 用來設置是否允許瀏覽器中修改 cookie,如果通過瀏覽器修改設置過 httpOnly=truecookie,則會增加一條同名 cookie,原來的 cookie 不會被修改;
  • Expires 用來設置過期時間,絕對時間,值爲一個 GMTUTC 格式的時間;
  • max-age 同樣用來設置過期時間,相對時間,值爲一個正整數,單位 s

cookie 默認不支持存儲中文,如果存儲中文需先使用 encodeURIComponent 方法進行轉譯,將轉譯後的結果存入 cookie,在瀏覽器獲取 cookie 需使用 decodeURIComponent 方法轉回中文。

2、Koa 中操作 cookie

Koa 是當下流行的 NodeJS 框架,是對原生 Node 的一個輕量的封裝,但是內部實現了快捷操作 cookie 的方法,下面是原生中對 cookie 的操作在 Koa 中的寫法。

// Koa 中操作 cookie
const Koa = require("koa");
const Router = require("koa-router");

// 創建服務和路由
const app = new Koa();
const router = new Router();

// 簽名需要設置 key
app.keys = ["shen"];

router.get("/read", (ctx, next) => {
    // 獲取 cookie
    let name = ctx.cookies.get(name) || "No name";
    let name = ctx.cookies.get(age) || "No age";
    ctx.body = `${name}-${age}`;
});

router.get("/write", (ctx, next) => {
    // 設置 cookie
    ctx.cookies.set("name", "panda", { domain: "panda.com" });
    ctx.cookies.set("age", 28, { maxAge: 10 * 1000, signed: true });
});

// 使用路由
app.use(router.routes());
app.listen(3000);

Koa 中將獲取和設置 cookie 的方法都掛在了 ctx 上下文對象的 cookies 屬性上,分別爲 getset

cookies.get 的參數爲獲取 cookie 的鍵名,返回值爲鍵對應的值,cookies.set 的第一個參數同樣爲 cookie 的鍵名,第二個參數爲鍵對應的值,第三個參數爲一個對象,用來配置該條 cookie 的規則,如 domainpath 和過期時間等,這裏 maxAge 值爲毫秒數。

注意:Koa 中設置的 cookie 默認不允許瀏覽器端通過 document.cookie 獲取,但是服務器也可以被欺騙,比如使用 postman 發送一個帶 Cookie 請求頭的請求,服務器可以通過設置簽名來預防,即添加 signed 選項並將值設置爲 true

3、Koa 操作 cookie 方法的原理

cookies 對象都是掛在 ctx 上來實現的,使用過 Koa 都知道如果要操作 ctx 就會用到中間件的思想,我們這就看看這兩個方法使用原生封裝的過程。

// Koa 中 ctx.cookies 對象 get 和 set 方法的原理
const Koa = require("koa");
const querystring = require("querystring");

const app = new Koa();

app.use(async (ctx, next) => {
    // 獲取 cookie
    const get = key => {
        let cookies = ctx.get("cookie") || "";
        return querystring.parse(result, "; ")[key];
    };

    // 設置 cookie,存儲所有的 cookie,等於 setHeader 中的第二個參數
    let cookies = [];
    const set = (key, val, options = {}) => {
        // 用於構造單條 cookie 和權限等設置的數組,默認存放這條 cookie 的鍵和值
        let single = [`${key}=${encodeURIComponent(val)}`];

        // 下面是配置
        if (options.domain) {
            arr.push(`domain=${options.domain}`);
        }

        if (options.maxAge) {
            arr.push(`Max-Age=${options.maxAge}`);
        }

        if (options.path) {
            arr.push(`path=${options.path}`);
        }

        if (options.httpOnly) {
            arr.push(`HttpOnly=true`);
        }

        // 將配置組合到 single 中後轉爲字符串存入 cookies
        cookies.push(single.join("; "));

        // 設置給瀏覽器
        ctx.set("Set-Cookie", cookies);
    }

    // 將獲取和設置 cookie 的方法掛在 cookies 對象上
    ctx.cookies = { get, set };

    await next();
});

get 方法內部獲取 cookie 請求頭的值並根據傳入的 key 獲取值,set 方法內,將傳入的鍵值和選項拼接成符合 cookie 的字符串,通過 Set-Cookie 響應頭設置給瀏覽器。

session 的基本使用

1、NodeJS 原生使用 session

正常 session 是存放在數據庫中的,我們這裏爲了方便就用一個名爲 session 的對象來代替。

// 原生中使用 session
const http = require("http");
const uuid = require('uuid/v1'); // 生成隨字符串
const querystring = require("querystring");

// 存放 session
const session = {};

// 創建服務
http.createServer((req, res) => {
    if (req.url === "/user") {
        // 取出 cookie 存儲的用戶 ID
        let userId = querystring.parse(req.headers["cookie"], "; ")["study"];

        if (userId) {
            if (session[userId].studyCount === 0) res.end("您的學習次數已用完");
            session[userId].studyCount--;
        } else {
            // 生成 userId
            userId = uuid();

            // 將用戶信息存入 session
            session[userId] = { studyCount: 30 };

            // 設置 cookie
            req.setHeader("Set-Cookie", [`study=${userId}`]);
        }

        // 響應信息
        res.end(`
            您的用戶 ID 爲 ${userId},
            剩餘學習次數爲:${session[userId].studyCount}
        `);
    } else {
        res.end("Not Found");
    }
}).listen(3000);

上面寫的案例是一個網校的場景,一個新用戶默認有 30 次學習機會,以後每次訪問服務器學習次數減 1,如果 studyCount 值爲 0,則提示學習次數用完,否則提示當前用戶的 ID 和剩餘學習次數,session 中存儲的是每一個用戶 ID 對應的剩餘學習次數,這樣就不會輕易的被修改學習剩餘次數,因爲服務器只認用戶 ID,再通過 ID 去更改對應的剩餘次數(當然忽略了別人冒充這個 ID 的情況,只能減,不能加),這樣就不會因爲篡改 cookie 而篡改用戶存在 session 中的數據,除非連整個數據庫都拖走。

2、Koa 中使用 session

我們接下來使用 Koa 實現和上面一摸一樣的場景,在 Koa 的社區中提供了專門操作 session 的中間件 koa-session,使用前需安裝。

// Koa 中使用 session
const Koa = require("koa");
const Router = require("koa-router");
const session = requier("koa-session");
const uuid = require("uuid/v1");

// 創建服務和路由
const app = new Koa();
const router = new Router();

// cookie 的簽名
app.keys = ["panda"];

// 使用 koa-session 中間件
app.use(session({
    key: "shen",
    maxAge: 10 * 1000
}, app));

router.get("/user", (ctx, next) => {
    // 取出 cookie 存儲的用戶 ID
    let userId = ctx.cookie("study");

    if (ctx.session.userId) {
        if (ctx.session[userId].studyCount === 0) res.end("您的學習次數已用完");
        ctx.session[userId].studyCount--;
    } else {
        // 生成 userId
        userId = uuid();

        // 將用戶信息存入 session
        ctx.session[userId] = { studyCount: 30 };

        // 設置 cookie
        ctx.cookies.set("study", userId);
    }

    // 響應信息
    ctx.body = `
        您的用戶 ID 爲 ${userId},
        剩餘學習次數爲:${session[userId].studyCount}
    `;
});

// 使用路由
app.use(router.routes());
app.listen(3000);

使用 Koakoa-session 以後,不再需要我們創建 session 對象進行存儲,並且 cookie-session 中間件幫我們封裝了 API 可以直接操作 mongoMySQL 數據庫,上面代碼中與用原生相比還增加了 cookiesession 的簽名和過期時間,比原生寫起來要方便很多。

總結

本篇內容更偏向於 cookiesession 在 NodeJS 中的使用,沒有過多的敘述理論性的內容,cookiesession 是相互依存的,也就是說共同使用的,現在已經有 JWT 的方案來替代,因爲相比較下有很多優點,但某些項目和特殊場景還在使用 cookiesession,所以還是寫了這一篇,如果對 JWT 感興趣可以看 通過一個案例理解 JWT

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章