个人博客开发系列:Vue.js + Koa.js项目中使用JWT认证

TDon

前言

JWT(JSON Web Token),是为了在网络环境间传递声明而执行的一种基于JSON的开放标准(RFC 7519)。

更多的介绍和说明,以及各种原理,我在此就不多赘诉了。JWT不是一个新鲜的东西,网上相关的介绍已经非常多了。不是很了解的可以在网上搜索一下相关信息。

源码

Talk is cheap. Show me the code.

工作流程

JWT本质来说是一个token。在前后端进行HTTP连接时来进行相应的验证。

  1. 博客的后台管理系统发起登录请求,后端服务器校验成功之后,生成JWT认证信息;
  2. 前端接收到JWT后进行存储;
  3. 前端在之后每次接口调用发起HTTP请求时,会将JWT放到HTTP的headers参数里的authorization中一起发送给后端;
  4. 后端接收到请求时会根据JWT中的信息来校验当前发起HTTP请求的用户是否是具有访问权限的,有访问权限时则交给服务器继续处理,没有时则直接返回401错误。

实现过程

1. 登录成功生成JWT

说明:以下代码只保留了核心代码,详细代码可在对应文件中查看,下同。

// /server/api/admin/admin.controller.js
const jwt = require('jsonwebtoken');
const config = require('../../config/config');

exports.login = async(ctx) => {
  // ...
  if (hashedPassword === hashPassword) {
    // ...
    // 用户token
    const userToken = {
      name: userName,
      id: results[0].id
    };
    // 签发token
    const token = jwt.sign(userToken, config.tokenSecret, { expiresIn: '2h' });
    // ...
  }
  // ...
}

2. 添加中间件校验JWT

// /server/middlreware/tokenError.js
const jwt = require('jsonwebtoken');
const config = require('../config/config');
const util = require('util');
const verify = util.promisify(jwt.verify);

/**
 * 判断token是否可用
 */
module.exports = function () {
  return async function (ctx, next) {
    try {
      // 获取jwt
      const token = ctx.header.authorization; 
      if (token) {
        try {
          // 解密payload,获取用户名和ID
          let payload = await verify(token.split(' ')[1], config.tokenSecret);
          ctx.user = {
            name: payload.name,
            id: payload.id
          };
        } catch (err) {
          console.log('token verify fail: ', err)
        }
      }
      await next();
    } catch (err) {
      if (err.status === 401) {
        ctx.status = 401;
        ctx.body = {
          success: 0,
          message: '认证失败'
        };
      } else {
        err.status = 404;
        ctx.body = {
          success: 0,
          message: '404'
        };
      }
    }
  }
}

3. Koa.js中添加JWT处理

此处在开发时需要过滤掉登录接口(login),否则会导致JWT验证永远失败。

// /server/config/koa.js
const jwt = require('koa-jwt');
const tokenError = require('../middlreware/tokenError');
// ...

const app = new Koa();

app.use(tokenError());
app.use(bodyParser());
app.use(koaJson());
app.use(resource(path.join(config.root, config.appPath)));

app.use(jwt({
  secret: config.tokenSecret
}).unless({
  path: [/^\/backapi\/admin\/login/, /^\/blogapi\//]
}));

module.exports = app;

4.前端处理

前端开发使用的是Vue.js,发送HTTP请求使用的是axios。

  1. 登录成功之后将JWT存储到localStorage中(可根据个人需要存储,我个人是比较喜欢存储到localStorage中)。

    methods: {
        login: async function () {
          // ...
    
          let res = await api.login(this.userName, this.password);
          if (res.success === 1) {
            this.errMsg = '';
            localStorage.setItem('DON_BLOG_TOKEN', res.token);
            this.$router.push({ path: '/postlist' });
          } else {
            this.errMsg = res.message;
          }
        }
      }

  2. Vue.js的router(路由)跳转前校验JWT是否存在,不存在则跳转到登录页面。

    // /src/router/index.js
    router.beforeEach((to, from, next) => {
      if (to.meta.requireAuth) {
        const token = localStorage.getItem('DON_BLOG_TOKEN');
        if (token && token !== 'null') {
          next();
        } else {
          next('/login');
        }
      } else {
        next();
      }
    });

  3. axios拦截器中给HTTP统一添加Authorization信息

    // /src/fetch/fetch.js
    axios.interceptors.request.use(
      config => {
        const token = localStorage.getItem('DON_BLOG_TOKEN');
        if (token) {
          // Bearer是JWT的认证头部信息
          config.headers.common['Authorization'] = 'Bearer ' + token;
        }
        return config;
      },
      error => {
        return Promise.reject(error);
      }
    );

  4. axios拦截器在接收到HTTP返回时统一处理返回状态

    // /src/main.js
    axios.interceptors.response.use(
      response => {
        return response;
      },
      error => {
        if (error.response.status === 401) {
          Vue.prototype.$msgBox.showMsgBox({
            title: '错误提示',
            content: '您的登录信息已失效,请重新登录',
            isShowCancelBtn: false
          }).then((val) => {
            router.push('/login');
          }).catch(() => {
            console.log('cancel');
          });
        } else {
          Vue.prototype.$message.showMessage({
            type: 'error',
            content: '系统出现错误'
          });
        }
        return Promise.reject(error);
      }
    );

总结

到这里整个流程就走完了。当然单纯的JWT并不是说绝对安全的,不过对于一个个人博客系统的认证来说还是足够的。

最后打个小广告。目前正在开发新版的个人博客中,包括前端后端两部分,都已在GitHub上开源,目前在逐步完善功能中。欢迎感兴趣的同学fork和star。

阅读 6.7k

DON的前端分享
前端开发,也了解一些后端的开发。分享个人项目和开发经验。
906 声望
42 粉丝
0 条评论
906 声望
42 粉丝
文章目录
宣传栏