前端开发搭建博客系统(二),jwt权鉴设计以及中途遇到的问题。献给前端

废话部分

上次废话太多,估计有些小伙伴没看太清楚。这次会精简很多。只拿关键的出来讲。

个人属于前端,后端看到了扣下留情。希望本文能帮助大家

项目地址:

前端:https://github.com/ht-sauce/dream

后端:https://github.com/ht-sauce/dream-admin

一、权鉴选型

这块没太多可以说的,基本百度的结果就是jwt

而eggjs官方是egg-jwt,其本质也是jsonwebtoken

egg-jwt地址:https://www.npmjs.com/package/egg-jwt

jsonwebtoken地址:https://www.npmjs.com/package/jsonwebtoken

eggjs的插件配置方面我就不多说了。太简单了(再次赞美eggjs)。

二、先写登录接口,保证生成权鉴信息

1、先放完整的登录接口函数

// 登录接口
async login() {
  const { ctx, service } = this;
  const userBusiness = service.consumer.user;
  const query = ctx.request.body; // 接口请求数据
  // 参数校验
  const rule = {
    account: { type: 'string', required: true },
    // 有format才能有message信息
    password: { type: 'string', required: true },
  };

  try {
    await ctx.validate(rule, query);
    // 优先处理用户是否存在
    const login = await userBusiness.userLogin(query);
    if (login) {
      // 登录之后生成口令,有效期24小时
      const token = ctx.helper.generate_token(query.account);
      // 前端肯定会需要用户信息,返回给前端用户基本信息
      const userInfo = {
        userInfo: await userBusiness.find(query),
        sign: token,
      };

      ctx.body = ctx.helper.result(userInfo);
    } else {
      ctx.body = ctx.helper.result('', -1, '用户不存在请注册');
    }
  } catch (e) {
    ctx.body = ctx.helper.result('', -1, e);
  }
}

2、原理解析

主要是先判断用户名密码,然后再是用自己封装的helper(eggjs的扩展)来生成口令。

至于helper就是进行统一的封装函数处理。

然后一并丢给前端。

三、中间件解析口令,进行校验拦截

1、中间件代码

'use strict';

module.exports = (options, app) => {
  return async function(ctx, next) {
    const token = ctx.request.header.authorization;

    try {
      if (token) {
        // 验证当前token
        const decode = app.jwt.verify(token, options.secret);
        // 验证用户信息是否正常
        if (!decode || !decode.account) {
          ctx.body = ctx.helper.result('', -1, '用户信息缺失', 1);
        }
        // 验证用户是否存在
        const user = await ctx.model.Consumer.User.findOne(
          { where: { account: decode.account } }
        );
        if (user) {
          // 如果口令有效期小于15分钟则发送新口令给前端
          if (decode.exp - Date.now() / 1000 < 60 * 15) {
            const token = ctx.helper.generate_token(decode.account);
            ctx.set('Authorization', token);
          }
          // 当所有验证都通过之后,可以正常访问
          await next();
        } else {
          ctx.body = ctx.helper.result('', -1, '用户信息验证失败', 1);
        }
      } else {
        ctx.body = ctx.helper.result('', -1, '口令验证失败', 1);
      }
    } catch (e) {
      console.log(e);
      ctx.body = ctx.helper.result('', -1, e, 1);
    }
  };
};

2、原理解析

大家仔细看代码。在各种验证之后最后只有await next()部分才是最终正确代码执行下去的。代表权鉴没有问题。

从前端的角度来看,无非就是if else之后通过了。

3、口令刷新问题

上面的代码有一个部分

// 如果口令有效期小于15分钟则发送新口令给前端
          if (decode.exp - Date.now() / 1000 < 60 * 15) {
            const token = ctx.helper.generate_token(decode.account);
            ctx.set('Authorization', token);
          }

这里比较关键,在于实现当用户不停操作之后能不会因为口令过期而突然退出登录。所以就需要校验口令的快过期时间。当快过期的时候发一个新的口令给前端。让前端刷新当前缓存的口令。注意不能时间太短。15分钟算一个比较合理的时间。

4、后端某些接口不校验口令直接过。

这个在后台管理的项目中基本不用太担心。但是开放式的博客就很有必要了。这里主要是看eggjs官方对于中间件的处理。

地址:https://eggjs.org/zh-cn/basics/middleware.html

我个人也对应进行配置

// 中间件配置
config.middleware = [ 'jwtAuthorize' ];
// 给jwtAuthorize中间件传入的参数
config.jwtAuthorize = {
  secret: 'daihaitian19940329',
  // 忽略指定路由
  ignore: [ `${config.dreamCustom.prefix}/noauth` ],
};

三、前端存储口令以及口令发送问题

1、登录接口

登录部分很简单,就是缓存用户信息就行了。主要核心在于ajax函数封装的地方

// 登录
login() {
  let data = {
    account: this.logindata.userName,
    password: userLoginPassword(this.logindata.password)
  };
  this.logining = true;
  this.axios
    .ajax({
      url: this.$api.consumer().user.login,
      data: data,
      method: "post"
    })
    .then(e => {
      this.logining = false;
      // 存储用户数据到缓存
      store.clearAll();
      store.set("user_info", e.data);
      console.log(e.data);
    })
    .catch(e => {
      this.logining = false;
      console.log(e);
    });

2、封装ajax函数,并且全局处理口令以及口令刷新的问题

代码放出核心封装部分

1、一个headers的封装。需要在前端有口令数据的情况将口令发送给后端

// 授权函数封装
const authorize = herders => {
  const user_info = store.get("user_info");
  if (user_info && user_info.sign) {
    herders.Authorization = user_info.sign;
    return herders;
  } else {
    return herders;
  }
};

2、口令刷新函数的封装

进行一系列的验证之后,如果后端返回的数据headers里面产生了口令需要及时刷新口令

// 刷新口令以及判断数据类型来判断是否退出登录
refresh_sign_or_out(res) {
  console.log(res);
  if (!res || !res.data) {
    this.logout();
    return false;
  }
  // type类型为1必定退出登录
  if (res.data.type === 1) {
    this.logout();
    return false;
  }
  if (res.headers.authorization) {
    const user_info = store.get("user_info");
    user_info.sign = res.headers.authorization;
    store.set("user_info", user_info);
  }
  return true;
}

四、缺陷回顾

1、目前最大的缺陷是口令只要生成了,那么就可以验证通过。

2、如果解决上一个问题,让口令唯一的话,那么就需要解决并发情况下口令更新,保证后续接口不会因为口令刷新而报错

3、口令唯一化问题

个人:不处理了。目前这样就可以保证并发的问题,只要不口令唯一化,那么就不用担心上述问题。而且个人属于前端,不打算深入探究了。

 

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