Node.js的Koa實現JWT使用者認證

唐金健發表於2019-02-16

版權宣告

轉載請告知並註明來源作者
作者唐金健
網路暱稱御焱
掘金知乎思否專欄優雅的前端

一、前置知識

二、環境

  • Microsoft Visual Studio 2017整合開發環境
  • Node.js v8.9.4Javascript執行環境

三、開始動手,一步步來完善

1、建立基礎的靜態資源伺服器、基礎架構

  以下是基本的程式碼,實現靜態伺服器,以及一個當token驗證異常時候的處理。
  下面我們將在這個基本程式碼下逐步增加註冊、登入、資訊的功能。

const path    = require(`path`);         // 用於處理目錄路徑
const Koa     = require(`koa`);          // web開發框架
const serve   = require(`koa-static`);   // 靜態資源處理
const route   = require(`koa-route`);    // 路由中介軟體
const jwt     = require(`jsonwebtoken`); // 用於簽發、解析`token`
const jwtKoa  = require(`koa-jwt`);      // 用於路由許可權控制
const koaBody = require(`koa-body`);     // 用於查詢字串解析到`ctx.request.query`
const app     = new Koa();

const website = {
    scheme: `http`,
    host: `localhost`,
    port: 1337,
    join: function () {
        return `${this.scheme}://${this.host}:${this.port}`
    }
}

/* jwt金鑰 */
const secret = `secret`;

/* 當token驗證異常時候的處理,如token過期、token錯誤 */
app.use((ctx, next) => {
    return next().catch((err) => {
        if (err.status === 401) {
            ctx.status = 401;
            ctx.body = {
                ok: false,
                msg: err.originalError ? err.originalError.message : err.message
            }
        } else {
            throw err;
        }
    });
});

/* 查詢字串解析到`ctx.request.query` */
app.use(koaBody());

/* 路由許可權控制 */
// 待辦事項……

/* POST /api/register 註冊 */
// 待辦事項……

/* GET /api/login 登入 */
// 待辦事項……

/* GET /api/info 資訊 */
// 待辦事項……


/* 靜態資源處理 */
app.use(serve(path.join(__dirname, `static`)));
/* 監聽伺服器埠 */
app.listen(website.port, () => {
    console.log(`${website.join()} 伺服器已經啟動!`);
});

  下面,我們將在註冊、登入、資訊的註釋底下新增實現的程式碼。

2、路由許可權控制

  註冊、登入介面、其它資源不需要認證,資訊介面需要認證。

/* 路由許可權控制 */
app.use(jwtKoa({ secret: secret }).unless({
    // 設定login、register介面,可以不需要認證訪問
    path: [
        /^/api/login/,
        /^/api/register/,
        /^((?!/api).)*$/   // 設定除了私有介面外的其它資源,可以不需要認證訪問
    ]
}));

3、註冊

/* POST /api/register 註冊 */
app.use(route.post(`/api/register`, async (ctx, next) => {
    const body = ctx.request.body;
    /*
     * body = {
     *  user : `御焱`,
     *  password : `123456`
     * }
     */

    // 判斷 body.user 和 body.password 格式是否正確
    // 待辦事項……

    // 判斷使用者是否已經註冊
    // 待辦事項……

    // 儲存到新使用者到資料庫中
    // 待辦事項……

    // 是否註冊成功
    let 是否註冊成功 = true;
    if (是否註冊成功) {
        // 返回一個註冊成功的JOSN資料給前端
        return ctx.body = {
            ok: true,
            msg: `註冊成功`,
            token: getToken({ user: body.user, password: body.password })
        }
    } else {
        // 返回一個註冊失敗的JOSN資料給前端
        return ctx.body = {
            ok: false,
            msg: `註冊失敗`
        }
    }
}));
/* 獲取一個期限為4小時的token */
function getToken(payload = {}) {
    return jwt.sign(payload, secret, { expiresIn: `4h` });
}

3、登入

/* GET /api/login 登入 */
app.use(route.get(`/api/login`, async (ctx, next) => {
    const query = ctx.request.query;
    /*
     * query = {
     *  user : `御焱`,
     *  password : `123456`
     * }
     */

    // 判斷 query.user 和 query.password 格式是否正確
    // 待辦事項……

    // 判斷是否已經註冊
    // 待辦事項……
    
    // 判斷姓名、學號是否正確
    // 待辦事項……
    
    return ctx.body = {
        ok: true,
        msg: `登入成功`,
        token: getToken({ user: query.user, password: query.password })
    }
}));


  前端獲取到token之後,可以儲存在任意本地儲存裡。

4、資訊

/* GET /api/info 資訊 */
app.use(route.get(`/api/info`, async (ctx, next) => {
    // 前端訪問時會附帶token在請求頭
    payload = getJWTPayload(ctx.headers.authorization)
    /*
     * payload = {
     * user : "御焱",
     * iat : 1524042454,
     * exp : 1524056854
     * }
     */

    // 根據 payload.user 查詢該使用者在資料庫中的資訊
    // 待辦事項……
    const info = {
        name: `御焱`,
        age: 10,
        sex: `男`
    }
    let 獲取資訊成功 = true;
    if (獲取資訊成功) {
        return ctx.body = {
            ok: true,
            msg: `獲取資訊成功`,
            data: info
        }
    } else {
        return ctx.body = {
            ok: false,
            msg: `獲取資訊失敗`
        }
    }
}));
/* 通過token獲取JWT的payload部分 */
function getJWTPayload(token) {
    // 驗證並解析JWT
    return jwt.verify(token.split(` `)[1], secret);
}


  訪問需要認證的介面時,需要在request頭附帶Authorization:Bearer [token]欄位。

相關文章