服务端编程——登录阶段(含JsonWebToken使用)

登录阶段

用户要传入的信息

  • 首先肯定要传入account和secret 也就是账号和密码,但在小程序里登录时不止有一种方式,比如可以直接从微信进去 这样就不需要密码了,因此密码要设置为可选填项

    • 添加对token的api以及TokenValidator校验器
  • 其次还要传入type,也就是进入途径,由于js没有枚举器,所以在api/v1/lib下新建文件enum.js来模拟枚举

    • 其实里面就是一个对象LoginType,其中有各种进入方式(type)对应的值
    • 设置一个isLoginType函数来判断当前的type是否属于里面的值 也就是是否合法;这里要遍历的是对象中的key值,还不太方便... 但可以将函数也定义在enum.js中,并将其放到LoginType对象里,之后都通过对象来调用,就能直接用this拿到所有key值:
function isThisType (val) {
  for (let key in this) {
    if (this[key] === val) {
      return true
    }
  }
  return false
}

const loginType = {
  USER_MINI_PROGRAM: 100,
  USER_EMAIL: 101,
  USER_MOBILE: 102,
  ADMIN_EMAIL: 200,
  isThisType
}

module.exports = {
  loginType
}

验证阶段

用户发送完相应请求后,要做以下几件事情:

  • 检验各项信息是否都按照要求填写,格式符不符合规范:设置一个TokenValidator校验器专门校验登录时的信息
    • 暂时只包括 account、secret、type(进入方式,刚刚说了验证方法)
  • 根据当前进入方式来选择不同的处理函数:用switch...case语句,调用不同的函数
    • 如:当前是用邮箱登录的 即type===LoginType.USER_EMAIL,则调用emailLogin函数(同步的),这个函数写在发送api的token.js文件中(当前文件)
    • emailLogin中调用Model中定义的方法verifyEmailPassword来验证账号密码是否正确:为什么要在Model中定义而不是直接卸载emailLogin中呢,因为这个方法是验证账号密码的,也就是要操控数据库中的数据,因此还是写sequelize中的Model中比较好(注意也要是同步的)
    • verifyEmailPassword中先看数据库中有没有输入的账号(User.findOne()),若不存在直接throw一个NotFound的error,存在的话就看当前输入的密码和数据库中相应密码是否匹配。这里要注意,数据库中保存的是加密的密码 而输入的是明文的,因此要用bcrypt的compareSync方法来判断密码是否匹配正确,不正确就抛出一个AuthorFailed的error
  • 注意以上所有函数要加上async和await让其同步,且返回值基本都是Promise,要得到它的确切返回值一定要在回调函数(.then())中拿,或者是在前面加上await把promise中的值计算出来
// /app/api/v1/token.js
router.post('/', async (ctx, next) => {
  const v = await new TokenValidator().validate(ctx)
  const type = v.get('body.type')
  const account = v.get('body.account')
  const secret = v.get('body.secret')
  switch (type) {
    case loginType.USER_EMAIL:
      await emailLogin(account, secret)
      breakb
    case loginType.USER_MINI_PROGRAM:
      break
    default:
      throw new global.errs.ParameterException('没有相应的处理函数')
  }
})

async function emailLogin (account, secret) {
  const res = await User.verifyEmailPassword(account, secret)
}

// /modules/user.js
class User extends Model {
    static async verifyEmailPassword (email, secret) {
      // 数据库查询是个异步操作 也要加await
      const res = await User.findOne({
        where: {
          email
        }
      })
      if (!res) {
        throw new global.errs.NotFound('账号不存在')
      }
      const correct = bcrypt.compareSync(secret, res.password)
      if (!correct) {
        throw new global.errs.AuthorFailed('密码不正确')
      }
      return res
    }
}

jwt——Json Web Token

就是一种便于客户端与服务器通信的令牌,详见

项目中使用

  • koa项目中引入basic-auth包,在/middlewares/auth.js中写一个Auth类用来校验jwt令牌

  • 引入base-auth中的basicAuth函数,将当前http请求对象传入即可得到token

    • 在koa项目中,中间件中的上下文ctx:ctx.request是被koa封装处理过后的请求对象,而ctx.req才是原生的http请求对象
  • 在要验证的地方,将这个Auth类导入,直接将刚刚写的函数当作中间件导入,注意顺序哦,一定要在执行操作之前就将其导入,这样才能起到截断的作用(在router函数中可以导入多个中间件)

  • Auth类中的校验函数里通过当前发送的http请求获取到token后,大致分为三步

    • 先看token和token.name(内容)是否存在,若不存在直接抛出Forbidden的error
    • 再看token的值是否合法——通过jsonwebtoken中的verify方法,传入两个参数 一个是当前获取到的token,另一个是配置文件中自己写好的secretKey
    • 第二步中的verify方法若错误则会抛出异常,所以要用try...catch语句来调用,其中catch中要查看当前error的名字来区分到底是 token过期 还是token不合法
  • token通过校验成功之后,会返回一个对象,内容就是在生成token的时候自己传进去的第一个参数(想要token携带的信息),可以将其保存到ctx中,在后续中间件都可以使用到

  get m () {
    return async (ctx, next) => {
      const errMsg = 'token不合法'
      // ctx.request === koa 对请求封装了
      // ctx.req === 原生的http请求
      const userToken = basicAuth(ctx.req)
      if (!userToken || !userToken.name) {
        throw new global.errs.Forbidden(errMsg)
      }
      try {
        var decode = jwt.verify(userToken.name, global.config.security.secretKey)
      } catch (error) {
        if (error.name === 'TokenExpiredError') {
          throw new global.errs.Forbidden('token已过期')
        }
        throw new global.errs.Forbidden(errMsg)
      }
      ctx.auth = {
        uid: decode.uid,
        scope: decode.scope
      }
      await next()
    }
  }
posted @ 2020-10-22 00:56  TRY0929  阅读(241)  评论(0编辑  收藏  举报