koa使用jwt进行路由鉴权

koa如何使用koa-jwt与jsonwebtoken进行路由鉴权

!!本文使用nuxt.js、koa2作为开发环境,前端存储token的方式是cookie

相关知识

有关jwt的知识可以看下面这篇文章

JSON Web Token - 在Web应用间安全地传递信息

安装

先npm安装我们所需的模块

npm install koa-jwt jsonwebtoken cookieparser

注:koa-jwt是使用jsonwebtoken来进行令牌(token)对发放与验证对,cookieparser模块我们用来解析cookie

然后贴一个nuxt应用框架以koa为后台的初始index.js文件

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并使用它

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字符串的密钥

在你的接口文件中引入jsonwebtoken

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接口文件代码我就不贴出来了):

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

至此,我们的路由鉴权就已经弄好了,完结撒花:tada::tada:

拓展(还有问题)

路由鉴权是没问题了,但是如果我整个站的链接那么多,我不能只有/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则刷新令牌。

最后

如有错误,劳烦请指出,谢谢:pray::pray:

我来评几句
登录后评论

已发表评论数()

相关站点

热门文章