likes
comments
collection
share

egg-jwt生成token+校验+错误拦截+登录失效等

作者站长头像
站长
· 阅读数 18

egg-jwt是适配egg的jwt鉴权插件,在讲这个插件如何使用前,先回顾下jwt的原理。

Json web token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准((RFC 7519).该token被设计为紧凑且安全的,特别适用于分布式站点的单点登录(SSO)场景。JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源,也可以增加一些额外的其它业务逻辑所必须的声明信息,该token也可直接被用于认证,也可被加密。

token获取流程

  • 浏览器向服务器请求token口令
  • 服务器创建token,并返回给浏览器
  • 浏览器接受到服务器返回的token,并保存在本地(cookie、localStorage、sessionStorage均可)
  • 浏览器向服务器发送的所有请求在headers中都带上token,供服务器校验服务器接收到请求,校验附带的tokent口令,校验通过则做后续工作,否则直接拒绝请求;

安装egg-jwt

npm i egg-jwt -S

配置plugin.js

jwt: { // 实现登录 token
    enable: true,
    package: 'egg-jwt',
  },

配置config/config.default.js

config.jwt = { // token 密码
    secret: '*******', // 可以自定义
    sign: {	//jwt.sign(***,***,[options,***])方法中,options的默认设置可以在这里配置;
      // 过期时间8小时
      expiresIn: 8 * (60 * 60)   //多少s后过期。actionToken.js中,jwt.sing(plyload,secret,{expiresIn:number})会被合并,调用时设置优先级更高;
    }
  };

生成token

exports.GenerateToken = (that, data) => {
  const { app, ctx } = that;

  // sign 的data 可以是对象
  const token = app.jwt.sign(data, app.config.jwt.secret);

  return token;
}

校验

const username = app.jwt.verify(token, app.config.jwt.secret);

如果大家要求不高的话,以上基本上就是大致的使用方法了;小伙伴们可以根据自己的需求修改就可以了;但是如果有比较高的需求的话那你就跟到后面开始走;在走的时候我们带着几个问题来;

错误方法:

然后再headers上如此携带token的值,这样确实可以验证token是否有效,但是返回给前台(浏览器)的时候会报错(token过期以后);所以这个方式就不是那么有效

router.post('/admin',jwt, controller.admin.index);

headers:{
  	// 切记 token 不要直接发送,要在前面加上 Bearer 字符串和一个空格
    'Authorization':`Bearer ${token}`
  }

我认为比较有效的验证方法:

在发送请求的时候(浏览器)携带在header中;

eggjs中获取token!如果没有token的话就让其去登录;

如果有token的话,通过 app.jwt.verify(token, app.config.jwt.secret) 进行验证,并赋值给username;如果过期的话就会在catch中监听到

然后就是解密以后通过token去表格中查询用户,如果能查到就statistics对比timer看是否一致,如果一致的话说明没有过期;反之过期;

如果没查到那就是没有改账户了!

statistics和timer对比问题: 在登录的时候,我们保留一个时间戳(秒)更新到用户列表中和更新到token中 如:

// 这个是封装的一个方法,跟上面 生成token是一样的
helper.GenerateToken(this, { username, password, timer })

这样时间timer就保持一致了,如果用户在其他设备上登录时,statistics就会变更,token不一致时时间也就不一致了,所以就没得问题!多设备问题;

我的表示可能有些问题,大家慢慢研究应该是能看懂意思的!

/**
 * 验证token是否有效及过期
 * @param {*} that this
 * @returns code 401 过期/未登录
 */
exports.verification = async (that) => {
  const { app, ctx } = that;
  const token = ctx.request.header.token;

  if (!token) {
    ctx.body = {
      msg: '你还未登录,请登录',
      code: 401, data: null
    };
    return { code: 401, msg: '你还未登录,请登录', data: null };
  } else {
    let username = null;
    try {
      username = app.jwt.verify(token, app.config.jwt.secret);
      if (!username) {
        return { code: 305, data: error, msg: '登录过期' };
      };
    } catch (error) {
      ctx.body = {
        msg: '登录过期',
        code: 305,
        data: null
      };
      return { code: 305, data: error, msg: '登录过期' };
    }

    if (username) {
      try {
        const user = await app.mysql.get('user', { usernam: username.username })
        if (user) {
          if (user.statistics == username.timer) {
            return { code: 200, data: null, msg: '验证成功',token:username };
          }else {
            ctx.body = {
              msg: '登录过期',
              code: 305,
              data: null
            };
            return { code: 305, data: null, msg: '登录过期' };
          }
        } else {
          ctx.body = {
            msg: '未查到账号,请联系管理员',
            code: 401,
            data: null
          };
          return { code: 401, data: null, msg: '未查到账号,请联系管理员' };
        }
      } catch (error) {
        ctx.body = {
          msg: error,
          code: 500,
          data: null
        };
        return { code: 500, data: error, msg: error };
      }
    }
  }

}

登录代码

这儿主要是更新了statistics字段以及获取token;没次去登录的时候都会去更新statistics字段,为的是防止不同设备登录的情况;

  // 登录
  async login() {
    const { ctx, app } = this;
    // post请求传来的参数
    const { username, password } = ctx.request.body;

    if (!username) {
      return ctx.body = {
        code: 400,
        msg: '账号不能为空',
        data: null
      }
    }
    if (!password) {
      return ctx.body = {
        code: 400,
        msg: '密码不能为空',
        data: null
      }
    }

    let body = {
      code: 200,
      data: null,
      token: null,
      msg: ''
    }
    try {
      const results = await app.mysql.get('user', { usernam: username });

      if (results) {
        if (password == results.password) {
          const timer = Math.floor(new Date().getTime() / 1000);
          // 生成token
          body.token = helper.GenerateToken(this, { username, password, timer });
          body.msg = '登录成功';
          body.data = null;
          // 更新时间戳
          await app.mysql.update('user', {statistics:timer},{where:{usernam:username}});
        } else {
          body.token = null;
          body.msg = '密码输入错误';
          body.data = null;
        }
      } else {
        body.code = 201;
        body.msg = '未注册,请联系管理员';
      }
    } catch (error) {
      body.code = 500;
      body.msg = error;
    }

    ctx.body = body;
  }

总结

  • 查看header里面是否携带token
  • 校验token获取账号和密码
  • 根据获取到的账号查询拿到最近的登录时间(statistics)
  • 对比时间是否一致并做出相对于的响应

往期文章

转载自:https://juejin.cn/post/7210574525666672677
评论
请登录