去年我写了一篇介绍 jwt
的文章。
文章指出如果没有特别的用户注销及单用户多设备登录的需求,可以使用 jwt,而 jwt 的最大的特征就是无状态,且不加密。
除了用户登录方面外,还可以使用 jwt 验证邮箱验证码,其实也可以验证手机验证码,但是鉴于我囊中羞涩,只能验证邮箱了。
另外,我已在我的试验田进行了实践,不过目前前端代码写的比较简陋,甚至没有失败的回馈提示。至于为什么前端写的简陋,完全是因为前端的代码量相比后端来讲实在过于庞大…
另外,如果你熟悉 graphql,也可以在本项目的 graphql-playground 中查看效果。
本文地址 shanyue.tech/post/jwt-an…
发送验证码
校验之前,需要配合一个随机数供邮箱和短信发送。使用以下代码片段生成一个六位数字的随机码,你也可以把它包装为一个函数
1 | 复制代码const verifyCode = Array.from(Array(6), () => parseInt((Math.random() * 10))).join('') |
如果使用传统有状态的解决方案,此时需要在服务端维护一个用户邮箱及随机码的键值对,而使用 jwt
也需要给前端返回一个 token,随后用来校验验证码。
我们知道 jwt 只会校验数据的完整性,而不对数据加密。此时当拿用户邮箱及校验码配对时,但是如果都放到 payload
中,而 jwt
使用明文传输数据,校验码会被泄露
1 | 复制代码// 放到明文中,校验码泄露 |
那如何保证校验码不被泄露,而且能够正确校验数据呢
我们知道 secret 是不会被泄露的,此时把校验码放到 secret 中,完成配对
1 | 复制代码// 再给个半小时的过期时间 |
在服务端发送邮件的同时,把 token 再传递给前端,随注册时再发送到后端进行验证,这是我项目中关于校验的 graphql
的代码。如果你不懂 graphql 也可以把它当做伪代码,大致应该都可以看的懂
1 | 复制代码type Mutation { |
1 | 复制代码const Mutation = { |
题外话,发送邮件也有几个问题需要思考一下,不过这里先不管它了,以后实现了再写篇文章总结一下
- 如果邮件由服务提供,如何考虑异步服务和同步服务
- 消息队列处理,发邮件不要求可靠性,更像是 UDP
- 为了避免用户短时间内大量邮件发送,如何实现限流 (RateLimit)
题外题外话,一般发送邮件或者手机短信之前需要一个图片校验码来进行用户真实性校验和限流。而图片校验码也可以通过 jwt 进行实现
注册
注册就简单很多了,对客户端传入的数据进行邮箱检验,校验成功后直接入库就可以了,以下是 graphql
的代码
1 | 复制代码type Mutation { |
1 | 复制代码const Mutation = { |
这里有一个细节,对入库的密码使用 MD5
与一个参数 salt
做了不可逆处理
1 | 复制代码function hash (str) { |
题外话,
salt
是否可以与JWT
的secret
设置为同一字符串?
再题外话,这里的输入正确邮箱的 Error 明显不应该发送至 Sentry (报警系统),而有的 Error 的信息可以直接显示在前端,如何对 Error 进行规范与分类
校验码由传统方法实现与 jwt 比较
如果使用传统方法,只需要一个 key/value 数据库,维护手机号/邮箱与检验码的对应关系即可实现,相比 jwt 而言要简单很多。
登录
一个用 jwt
实现登录的 graphql
代码,把 user_id
与 user_role
置于 payload 中
1 | 复制代码type Mutation { |
1 | 复制代码const Mutation = { |
关注公众号山月行,记录我的技术成长,欢迎交流
本文转载自: 掘金