koa使用jwt進行路由鑑權

koa如何使用koa-jwt與jsonwebtoken進行路由鑑權

!!本文使用nuxt.js、koa2做爲開發環境,前端存儲token的方式是cookie前端

相關知識

有關jwt的知識能夠看下面這篇文章

JSON Web Token - 在Web應用間安全地傳遞信息ios

安裝

先npm安裝咱們所需的模塊web

npm install koa-jwt jsonwebtoken cookieparser
注:koa-jwt是使用jsonwebtoken來進行令牌(token)對發放與驗證對,cookieparser模塊咱們用來解析cookie

而後貼一個nuxt應用框架以koa爲後臺的初始index.js文件npm

const Koa = require("koa");
const consola = require("consola");
const { Nuxt, Builder } = require("nuxt");

const app = new Koa();

// Import and Set Nuxt.js options
const config = require("../nuxt.config.js");
config.dev = app.env !== "production";

async function start() {
    // Instantiate nuxt.js
    const nuxt = new Nuxt(config);

    const {
        host = process.env.HOST || "127.0.0.1",
        port = process.env.PORT || 3000
    } = nuxt.options.server;

    // Build in development
    if (config.dev) {
        const builder = new Builder(nuxt);
        await builder.build();
    } else {
        await nuxt.ready();
    }

    app.use((ctx, next) => {
        ctx.status = 200;
        ctx.respond = false; // Bypass Koa's built-in response handling
        ctx.req.ctx = ctx; // This might be useful later on, e.g. in nuxtServerInit or with nuxt-stash
        nuxt.render(ctx.req, ctx.res);
    });

    app.listen(port, host);
    consola.ready({
        message: `Server listening on http://${host}:${port}`,
        badge: true
    });
}

start();

使用

後端(koa)代碼:

咱們在index.js中引入koa-jwt並使用它json

const jwt = require("koa-jwt");
const SECRET = "Public secret";

app.use(
    jwt({ secret: SECRET }).unless({
        path: \[/^\\/api\\/login/, /^\\/api\\/register/\]
    })
);
注:unless是jwt的一個排除對應的url鑑權的方法,咱們排除了login與logout路由鑑權,SECRET是保存在後臺用來讓jsonwebtoken生成token字符串的密鑰

在你的接口文件中引入jsonwebtokenaxios

const jsonwebtoken = require("jsonwebtoken");

在/api/login登陸接口中若是登陸驗證成功則生成令牌併發送給前端,並在cookie中設置值後端

if(result){
    let token = jsonwebtoken.sign(payload,SECRET,
        { expiresIn: "1h" }
    );
    ctx.cookies.set("auth", JSON.stringify(token), {
        maxAge: 60000 * 60,
        overwrite: true
    });
    ctx.body={
        token: token,
        user: {}
    }
}
注:payload表示有效負載,若是你有看上面那篇有關jsonwebtoken的文章,我想你是明白的~ SECRET則是咱們在index.js文件相同的密鑰字符串,expiresIn是令牌有效時間,字符串形式,能夠放入1h,或者數字(毫秒),這裏有隨便提一下,cookie咱們的httponly選項要是true(默認是true)這是爲了防XSS(多少能防些)就不細說

這時候咱們的index.js文件就變成來這樣(api接口文件代碼我就不貼出來了):api

const Koa = require("koa");
const consola = require("consola");
const { Nuxt, Builder } = require("nuxt");
const bodyParser = require("koa-bodyparser");
const jwt = require("koa-jwt");
//引入登陸接口文件
const user = require("./api/user");
const SECRET = "Public secret";

const app = new Koa();

// Import and Set Nuxt.js options
const config = require("../nuxt.config.js");
config.dev = app.env !== "production";

async function start() {
    // Instantiate nuxt.js
    const nuxt = new Nuxt(config);

    const {
        host = process.env.HOST || "127.0.0.1",
        port = process.env.PORT || 3000
    } = nuxt.options.server;

    // Build in development
    if (config.dev) {
        const builder = new Builder(nuxt);
        await builder.build();
    } else {
        await nuxt.ready();
    }
    
    app.use(
        jwt({ secret: SECRET }).unless({
            path: \[/^\\/api\\/login/, /^\\/api\\/register/\]
        })
    );
    
    //使用接口文件
    app.use(user.routes()).use(user.allowedMethods());

    app.use((ctx, next) => {
        ctx.status = 200;
        ctx.respond = false; // Bypass Koa's built-in response handling
        ctx.req.ctx = ctx; // This might be useful later on, e.g. in nuxtServerInit or with nuxt-stash
        nuxt.render(ctx.req, ctx.res);
    });

    app.listen(port, host);
    consola.ready({
        message: `Server listening on http://${host}:${port}`,
        badge: true
    });
}

start();

如今咱們已經作好後臺的鑑權邏輯了,只要有請求進來除了/api/login與/api/register的url外都會先通過jwt進行權限安全

前端代碼:

前端的處理邏輯是登陸成功後從返回的結果中將用戶信息保存在store中,而後在axios中添加請求攔截器,每次請求的時候在請求頭中添加authorization字段,字段內容是"Bearer "+token,Bearer與token之間有空格,添加錯誤攔截器,jsonwebtoken在驗證令牌錯誤時會發出錯誤,且相應code爲401。這裏我直接貼代碼了,就不細說了服務器

axios.js文件

export default function(app) {
    let axios = app.$axios;
    axios.onRequest(config => {
        if (app.store.state.authUser) {
            config.headers.authorization =
                "Bearer " + app.store.state.authUser.token;
        }
    });

    axios.onError(error => {
        const code = parseInt(error.response && error.response.status);
        if (code === 401) {
            //錯誤處理邏輯
        }
    });
}

store的index.js文件

const cookieparser = process.server ? require("cookieparser") : undefined;

export const state = () => ({
    authUser: null
});

export const mutations = {
    SET_USER(state, user) {
        state.authUser = user;
    }
};

export const actions = {
    // nuxtServerInit是由Nuxt.js在服務器渲染每一個頁面以前調用的
    nuxtServerInit({ commit }, { req }) {
        let auth = null;
        if (req.headers.cookie) {
            const parsed = cookieparser.parse(req.headers.cookie);
            auth = new Buffer(
                parsed.authUser.split(".")[1],
                "base64"
            ).toString();
            try {
                auth = JSON.parse(auth);
                auth.token = parsed.authUser;
                // console.log(auth);
                commit("SET_USER", auth);
            } catch (err) {
                // 未找到有效的cookie
                console.log(err);
            }
        }
    },
    async login({ commit }, { username, password }) {
        try {
            const { data } = await this.$axios.post("/api/login", {
                name: username,
                password: password
            });
            if (data.result) {
                commit("SET_USER", data);
            }
        } catch (error) {
            if (error.response && error.response.status === 401) {
                throw new Error("Bad credentials");
            }
            throw error;
        }
    },
};
注:cookieparser模塊用來解析cookie

至此,咱們的路由鑑權就已經弄好了,完結撒花🎉🎉

拓展(還有問題)

路由鑑權是沒問題了,可是若是我整個站的連接那麼多,我不能只有/api/login跟/api/logout不用鑑權,其餘的都要,我一個個都往unless裏面加?也能夠,可是以爲有點麻煩。還有就是若是令牌到期了怎麼辦,我要是在後臺編輯着東西,忽然權限過時了,那不是很尷尬。因此咱們須要只要在限制的時間內有操做後臺,就更新令牌。

咱們修改一下後臺koa中間件,直接貼index.js代碼

const Koa = require("koa");
const consola = require("consola");
const { Nuxt, Builder } = require("nuxt");
const bodyParser = require("koa-bodyparser");
const jsonwebtoken = require("jsonwebtoken");
//引入登陸接口文件
const user = require("./api/user");
const SECRET = "Public secret";

const app = new Koa();

// Import and Set Nuxt.js options
const config = require("../nuxt.config.js");
config.dev = app.env !== "production";

async function start() {
    // Instantiate nuxt.js
    const nuxt = new Nuxt(config);

    const {
        host = process.env.HOST || "127.0.0.1",
        port = process.env.PORT || 3000
    } = nuxt.options.server;

    // Build in development
    if (config.dev) {
        const builder = new Builder(nuxt);
        await builder.build();
    } else {
        await nuxt.ready();
    }
    //修改的地方在這裏
    app.use((ctx, next) => {
        if (
            ctx.url.match(/^\/api\/login/) ||
            ctx.url.match(/^\/api\/register/)
        ) {
            return next();
        }
        if (ctx.url.match(/^\/api/)) {
            //路由判斷是否以/api開頭的url,是則進行鑑權,不然直接輸入內容
            let authorization = ctx.headers.authorization,
                token;
            if (ctx.headers) {
                if (!authorization) {
                    ctx.status = 401;
                    return (ctx.body = "Bad permissions");
                }
                token = authorization.split(" ")[1];
                try {
                    let decoded = jsonwebtoken.verify(token, SECRET),refresh;
                        //這裏寫刷新令牌邏輯,refresh是判斷是否到達刷新令牌時間結果
                    if (refresh) {
                        //在刷新時間範圍內請求則刷新令牌
                        try {
                            let newToken = jsonwebtoken.sign(
                                user,
                                SECRET,
                                {
                                    expiresIn: "1h"
                                }
                            );
                            ctx.cookies.set(
                                "authUser",
                                JSON.stringify(newToken),
                                {
                                    maxAge: 60000 * 60,
                                    overwrite: true
                                }
                            );
                        } catch (err) {
                            console.log(err);
                        }
                    }
                } catch (err) {
                    ctx.status = 401;
                    return (ctx.body = "Bad permissions");
                }
            }
            return next();
        } else {
            ctx.status = 200;
            ctx.respond = false; // Bypass Koa's built-in response handling
            ctx.req.ctx = ctx; // This might be useful later on, e.g. in nuxtServerInit or with nuxt-stash
            nuxt.render(ctx.req, ctx.res);
        }
    });

    app.use(user.routes()).use(user.allowedMethods());

    app.listen(port, host);
    consola.ready({
        message: `Server listening on http://${host}:${port}`,
        badge: true
    });
}

start();

如今終於完了,咱們作到了只要請求除了/api/login與/api/register以外都任意以/api開頭的url都會進行鑑權,令牌也會持續更新不怕用着用着就過時了。

隨便說說刷新令牌邏輯,我用的是設置令牌過時時間爲2小時,刷新令牌的時間是1小時,也就是隻要你離過時1小時前不操做後臺,令牌就會過時,以當前時間是否超過令牌發放時間加一小時的和來判斷是否須要刷新令牌,即currentTime > refreshInterval+iat;結果是true則刷新令牌。

最後

若有錯誤,勞煩請指出,謝謝🙏🙏

相關文章
相關標籤/搜索