json web token 实践登录以及校验码验证

栏目: 前端 · 发布时间: 5年前

内容简介:去年我写了一篇介绍文章指出如果没有特别的用户注销及单用户多设备登录的需求,可以使用 jwt,而 jwt 的最大的特征就是无状态,且不加密。除了用户登录方面外,还可以使用 jwt 验证邮箱验证码,其实也可以验证手机验证码,但是鉴于我穷,只能验证邮箱了。

去年我写了一篇介绍 jwt文章

文章指出如果没有特别的用户注销及单用户多设备登录的需求,可以使用 jwt,而 jwt 的最大的特征就是无状态,且不加密。

除了用户登录方面外,还可以使用 jwt 验证邮箱验证码,其实也可以验证手机验证码,但是鉴于我穷,只能验证邮箱了。

另外,我已在我的试验田进行了实践,不过目前前端代码写的比较简陋,甚至没有失败的回馈提示。至于为什么前端写的简陋,完全是因为前端的代码量相比后端来讲实在过于庞大... 特别是有时候还需要调样式,参考我的上一篇文章 使用纯 CSS 实现仿 Material Design 的 input 过渡效果

另外,如果你熟悉 graphql,也可以在本项目的graphql-playground 中查看效果。

本文地址 shanyue.tech/post/jwt-an…

发送验证码

校验之前,需要配合一个随机数供邮箱和短信发送。使用以下代码片段生成一个六位数字的随机码,你也可以把它包装为一个函数

const verifyCode = Array.from(Array(6), () => parseInt((Math.random() * 10))).join('')
复制代码

如果使用传统有状态的解决方案,此时需要在服务端维护一个用户邮箱及随机码的键值对,而使用 jwt 也需要给前端返回一个 token,随后用来校验验证码。

我们知道 jwt 只会校验数据的完整性,而不对数据加密。此时当拿用户邮箱及校验码配对时,但是如果都放到 payload 中,而 jwt 使用明文传输数据,校验码会被泄露

// 放到明文中,校验码泄露
jwt.sign({ email, verifyCode }, config.jwtSecret, { expiresIn: '30m' })
复制代码

那如何保证校验码不被泄露,而且能够正确校验数据呢

我们知道 secret 是不会被泄露的,此时把校验码放到 secret 中,完成配对

// 再给个半小时的过期时间
const token = jwt.sign({ email }, config.jwtSecret + verifyCode, { expiresIn: '30m' })
复制代码

在服务端发送邮件的同时,把 token 再传递给前端,随注册时再发送到后端进行验证,这是我项目中关于校验的 graphql 的代码。如果你不懂 graphql 也可以把它当做伪代码,大致应该都可以看的懂

const typeDef = `
  type Mutation {
    # 发送邮件
    # 返回一个 token,注册时需要携带 token,用以校验验证码
    sendEmailVerifyCode (
      email: String! @constraint(format: "email")
    ): String!
  }
`

const Mutation = {
  async sendEmailVerifyCode (root, { email }, { email: emailService }) {
    // 生成六个随机数
    const verifyCode = Array.from(Array(6), () => parseInt((Math.random() * 10))).join('')
    // TODO 可以放到消息队列里,但是没有多少量,而且本 Mutation 还有限流,其实目前没啥必要...
    // 与打点一样,不关注结果
    emailService.send({
      to: email, 
      subject: '【诗词弦歌】账号安全——邮箱验证',
      html: `您正在进行邮箱验证,本次请求的验证码为:<span style="color:#337ab7">${verifyCode}</span>(为了保证您帐号的安全性,请在30分钟内完成验证)\n\n诗词弦歌团队`
    })
    return jwt.sign({ email }, config.jwtSecret + verifyCode, { expiresIn: '30m' })
  }
}
复制代码

题外话,发送邮件也有几个问题需要思考一下,不过这里先不管它了,以后实现了再写篇文章总结一下

  1. 如果邮件由服务提供,如何考虑异步服务和同步服务
  2. 消息队列处理,发邮件不要求可靠性,更像是 UDP
  3. 为了避免用户短时间内大量邮件发送,如何实现限流 (RateLimit)

题外题外话,一般发送邮件或者手机短信之前需要一个图片校验码来进行用户真实性校验和限流。而图片校验码也可以通过 jwt 进行实现

注册

注册就简单很多了,对客户端传入的数据进行邮箱检验,校验成功后直接入库就可以了,以下是 graphql 的代码

const typeDef = `
  type Mutation {
    # 注册
    createUser (
      name: String!
      password: String!
      email: String! @constraint(format: "email")
      verifyCode: String!
      # 发送邮件传给客户端的 token
      token: String!
    ): User!
  }
`
const Mutation = {
  async createUser (root, { name, password, email, verifyCode, token }, { models }) {
    const { email: verifyEmail } = jwt.verify(token, config.jwtSecret + verifyCode)
    if (email !== verifyEmail) {
      throw new Error('请输入正确的邮箱') 
    }
    const user = await models.users.create({
      name,
      email,
      // 入库时密码做了加盐处理
      password: hash(password)
    })
    return user
  }
}
复制代码

这里有一个细节,对入库的密码使用 MD5 与一个参数 salt 做了不可逆处理

function hash (str) {
  return crypto.createHash('md5').update(`${str}-${config.salt}`, 'utf8').digest('hex')
}
复制代码

题外话, salt 是否可以与 JWTsecret 设置为同一字符串?

再题外话,这里的输入正确邮箱的 Error 明显不应该发送至 Sentry (报警系统),而有的 Error 的信息可以直接显示在前端,如何对 Error 进行规范与分类

校验码由传统方法实现与 jwt 比较

如果使用传统方法,只需要一个 key/value 数据库,维护手机号/邮箱与检验码的对应关系即可实现,相比 jwt 而言要简单很多。

登录

一个用 jwt 实现登录的 graphql 代码,把 user_iduser_role 置于 payload 中

const typeDef = `
  type Mutation {
    # 登录,如果返回 null,则登录失败
    createUserToken (
      email: String! @constraint(format: "email")
      password: String!
    ): String
  }
`

const Mutation = {
  async createUserToken (root, { email, password }, { models }) {
    const user = await models.users.findOne({
      where: {
        email,
        password: hash(password)
      },
      attributes: ['id', 'role'],
      raw: true
    })
    if (!user) {
      // 返回空代表用户登录失败
      return
    }
    return jwt.sign(user, config.jwtSecret, { expiresIn: '1d' })
  }
}
复制代码

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

精通正则表达式

精通正则表达式

[美] Jeffrey E.F.Friedl / 余晟 / 电子工业出版社 / 2012-7 / 89.00元

《精通正则表达式(第3版)》内容简介:随着互联网的迅速发展,几乎所有工具软件和程序语言都支持的正则表达式也变得越来越强大和易于使用。《精通正则表达式(第3版)》是讲解正则表达式的经典之作。《精通正则表达式(第3版)》主要讲解了正则表达式的特性和流派、匹配原理、优化原则、实用诀窍以及调校措施,并详细介绍了正则表达式在Perl、Java、.NET、PHP中的用法。《精通正则表达式(第3版)》自第1版开......一起来看看 《精通正则表达式》 这本书的介绍吧!

CSS 压缩/解压工具
CSS 压缩/解压工具

在线压缩/解压 CSS 代码

RGB CMYK 转换工具
RGB CMYK 转换工具

RGB CMYK 互转工具

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具