【扫码系统】设计核心
【扫码系统】设计核心
学习核心
- 如何设计一个【扫码】系统?(系统的核心功能)
- 沟通对齐
- ① 需求分析:模拟QQ扫码登录流程,实现PC端扫码登录过程
- ② 请求量分析、精准度分析:对于扫码场景而言,它的流量并不想秒杀场景那样需要考虑瞬时并发,相对来说是比较平缓的流量访问,因此更应将重点放在业务状态流转分析
- ③ 难点分析:扫码登录的状态流转和安全性问题
- 整体设计
- ① 服务设计(分层设计):
- 接入层:网关服务
- 业务层:登录服务
- 存储层:Redis 存储
- ② 存储设计(存储选型)
- 使用Redis数据库存储(KV存储)
- ③ 业务设计(业务流程):理解扫码的整体流程
- (1)PC端向登录服务申请一个二维码ID
- (2)PC端通过二维码ID调用二维码服务生成二维码信息(例如关联二维码图片的url),并轮询二维码状态
- (3)手机端扫码,拿到二维码ID
- (4)手机端发起扫码请求
- (5)手机端确认登录
- ① 服务设计(分层设计):
- 要点分析(或难点分析)
- ① 存储信息的状态变化
- 【生成时】:初始化二维码状态为【待扫码】
- 【扫码时】:手机端扫码后二维码状态切换为【待确认】
- 【确认后】:手机端确认登录后二维码状态切换为【激活】,PC端通过轮询校验二维码状态如果为激活则说明登录成功进入下一步操作
- ② 安全问题
- 设备关联设计:将二维码ID与设备进行关联,可以通过校验设备信息来约束token的作用范围
- 临时token设计:临时token的引入是基于扫码和确认两个步骤分离,需确保扫码和确认是同一个设备以此确保流程流转的安全性
- 过期时间设计:给token设置过期时间,一方面是为了及时清理存储,另一方面也是通过时间控制机制来减少恶人作恶时间
- 例如初始化设置码的过期时间为1分钟,扫码确认登录时间控制在2分钟,登录确认后可以将过期时间设置为5分钟以确保PC端可以轮询到登录状态并更新
- 此处对于token的清理操作可以依赖于过期机制,也可让客户端执行完逻辑之后主动删除
- ① 存储信息的状态变化
- 总结陈述
- 关注登录状态同步的状态转移过程和安全性问题
- 状态转移:理解不同阶段的Redis信息的变化
- 安全性问题:通过设备绑定、临时token、过期时间等机制来提升扫码登录的安全性
- 关注登录状态同步的状态转移过程和安全性问题
学习资料
🟢【扫码系统】场景核心
扫码登录是比较通用的方案,例如日常生活中常见的QQ、Wechat 二维码扫码登录等场景。所谓的二维码实际上就是字符串,通过一些特定的工具包转化绘制为二维码图片信息
🚀【扫码系统】场景实战
1.沟通对齐
此处的沟通对齐方向,主核心方向是需求分析、请求量分析、精准度分析、难点/要点分析,可能还有涉及到其他的一些容量、设计等方面的对齐
① 需求分析
扫码登录场景
例如QQ、Wechat 二维码扫码登录场景
业务流程分析
以手机扫码登录PC端QQ为例
① 点击登录,显示二维码信息
② 手机扫码,随后PC端显示提示在手机端确认登录流程
③ 手机端提示确认登录,在限定的等待时间范围内完成手机端的登录验证操作
④ 手机确认登录
⑤ PC登录成功
整个业务流程实际上对照就是PC端展示二维码,然后通过手机扫码确认(此处有一个扫描确认,如果扫描成功PC端回显扫描成功,如果扫描是在则PC端提示可能的失败原因),如果手机端登录验证通过则PC显示登录成功
② 请求量分析
一般的业务系统中,业务请求量是考察的重点。但此处对于扫码登录场景来说,更主要的是关注其流程。因为扫码场景和秒杀场景不一样,它并不是一个瞬时的活动流量,而是一个长期比较均衡的值
此处可以确认系统访问的QPS设计,此处影响到存储二维码ID的组件,可以进一步考虑单机、集群部署架构
③ 精准度分析
扫码场景没有像是秒杀场景这样的精准度要求
④ 难点/要点分析
2.整体设计(架构设计)
要点:
- 接入层:网关服务
- 业务层:登录服务
- 存储层:Redis存储
① 服务设计(分层设计)
扫码登录本身涉及的服务不多,因此重点关注网关服务(基本上每个业务系统都会涉及到的服务)和登录服务。除了服务本身之外还需要区分不同的设备端(手机端、PC端),此处PC端是需要登录的一方,而手机端则是已经登录,想通过扫码帮助PC登录的一方
② 存储设计(存储选型)
此处涉及存储的是二维码信息,在扫码登陆过程中,这个二维码的状态是不断变化的,最终确认之后会生成一个pc token,一个设备的登陆凭证就是一个token(也就是一个设备一个token),登陆时候使用这个token去登陆鉴权。此处目前看不到任何需要关系型查询的地方,因此选用KV存储(说起KV存储,第一时间联想到Redis存储)
③ 业务设计(业务流程)
基于上述分析,进一步结合业务流程整合整体架构
- ① PC 端 向登录服务申请二维码ID
- 产生二维码ID
- 关联二维码ID和设备信息记录到存储中
- ② 生成和展示二维码
- 通过二维码ID调用二维码服务生成二维码URL
- PC端展示二维码信息
- 开始轮询二维码状态
- ③ 移动端扫描二维码拿到二维码ID
- ④ 移动端发起扫码请求
- 请求到达服务端,关联二维码ID和用户ID,并更新二维码对应的状态,生成一个临时token
- ⑤ 移动端确认登录
- 使用临时token确认登录
- 生成登录用的pc token
- 将pc token关联上二维码ID,并更新pc token状态为已激活
如果简化一点,此处对接二维码服务也可以由登录服务去对接,对于PC端来说只需要管登录服务要一个码(由登录服务根据二维码ID对接二维码服务生成二维码并返回url用于展示二维码),然后开始轮询
3.要点分析
① Redis 信息存储
结合业务流程理解二维码的状态信息变化:一开始申请二维码ID的时候,会生成一个二维码ID关联PC设备信息,此时还会记录一个Status状态信息(此处二维码状态处于【待扫码】状态)
随后手机端进行扫码,二维码状态切换为【待确认】状态
待手机端确认登录后,二维码状态切换为【激活】状态
因此对于Redis信息的存储内容有三个阶段:生成时、扫码时、确认后
(1)【生成时】Redis 数据
K-V 存储,此时 key 为 二维码ID,value 存储主要包括两个数据:PC端设备信息(deviceInfo:包括设备ID等相关设备信息,将其视作一个整体)、状态信息(从生成、待确认、激活、关闭等状态)
整合一下上述的信息,需要用到结构化的 json 字符串来存储,可以进一步得到基本的存储数据结构
{
"qrIDxxx":{
"deviceInfo": {
"deviceID": "xxxx", // 设备ID
"deviceType": "ios", // 设备类型
"position": "cn" // 地理位置
},
"status": 1 // 待扫码状态
}
}
还有一种是拆分成多行存储的方式,通过引入后缀来区分信息,例如 二维码ID_deviceInfo(表示存储设备信息)、二维码ID_status(表示存储状态信息),其中deviceInfo可能还包括多个基础信息,但这种拆分和整合的方式会复杂化设计,收益也有限,不如 json 格式设定简洁明了
(2)【扫码时】Redis 数据
通过扫码之后,可以在PC端看到基本的用户信息(例如用户账号、头像等),这说明扫码时调用登录服务拿到了账号信息,所以将账号信息传递过去了(很多实现中通过传递一个账户ID,待PC拿到之后可以通过ID获得基本信息),扫码成功后token的状态会变为待确认状态
{
"tmpTokenxxx":{
"accountId": "xxxxxx", // 账号ID
"deviceInfo": {
"deviceID": "xxxx", // 设备ID
"deviceType": "iphone", // 设备类型
"position": "shanghai" // 地理位置
},
"status": 2 // 待确认状态
}
}
在扫码之后还会生成一个临时token,这个临时token关联着手机端的设备信息:
{
"tmpTokenxxx":{
"deviceInfo": {
"deviceID": "xxxx", // 设备ID
"deviceType": "iphone", // 设备类型
"position": "shanghai" // 地理位置
}
}
}
(3)【确认后】Redis 数据
待手机端确认登录之后,二维码的状态会变为激活状态,生成一个pc token进行关联,并删除临时token
{
"qrIDxxx":{
"accountId": "xxxxxx", // 账号ID
"deviceInfo": {
"deviceID": "xxxx", // 设备ID
"deviceType": "ios", // 设备类型
"position": "cn" // 地理位置
},
"status": 3, // 激活状态
"pcToken": "xxxxxx"
}
}
上述操作为了保证原子性,都是基于lua脚本来执行的
② 安全性考虑
(1)设备关联设计
这里有个设计细节,就是token是有关联设备信息的。为什么要这样的,目的是在于希望一个token只能适用于一个设备,这样就算token泄露,只要坏人不知道你设备信息,依然无法登陆,多了一层保障。所以在后面登陆时,每一次都会传递设备信息用以校验
(2)临时token设计
思考一下为啥会有个确认登陆,一般来说扫码不就行了么,这里是有啥技术考虑吗? 这里其实最主要还是产品考虑,确认流程是必要的,如果仅仅是因为不小心扫一下,就登陆放行了,会存在危险隐患(例如扫码支付的场景,如果扫码成功就认为直接放行,可能会导致坏的后果)。现在还有厂家,加上了几秒之后才能确认,也一样是为了安全考虑,给给足用户充裕的时间让用户进行确认
因为有了确认登陆这个逻辑,所以就需要临时token,这样才能将确认和扫码关联起来:请求中携带了临时token,这个token之前只在扫码时候返回给过手机端,通过这个凭证,可以一定程度证明扫码的是你,现在确认登陆的还是你。同时,临时token是一次性的,只能用一次,无法再重复请求,这也进一步提高了安全性。
也就是说扫码成功返回生成的临时token,然后手机端再通过这个临时token确认登录(确保扫码和确认登录的是同一人)
(3)过期时间设计
如果长时间不扫码,码就会过期,通过这种时间控制机制,也能减少坏人作恶的计划。这个ttl
的设置也有多个阶段
- ① 二维码生成:在初始化 set 二维码ID的时候设置
ttl
(例如QQ一般是在1分钟左右) - ② 扫码后(等待确认):扫码之后会更新这个过期时间以等待确认,通常扫码后的过期时间可以稍微长一点(确认时间放宽一些,例如设置2分钟)
- ③ 激活:手机端确认登录后会更新二维码状态,此时PC也会不断轮询查询二维码状态以刷新页面(一般确认之后的过期时间也会更新得长一些,例如5分钟,让PC端有充裕的时间可以轮询到token,此处也可以依赖这个过期时间释放二维码ID这个key,也可以在PC端轮询确认登录成功后主动删除这个key)
除此之外,还要记得有个临时token,这个临时token在确认之后会被删除,如果长时间不点确认,他本身也是会过期的,因此临时token也会有个过期时间,不然出现异常就变成了泄露的内存了。临时token本身不能用来登陆,所以不一定非要和没确认的二维码一起过期,可以比二维码ID过期时间稍微长点,比如10分钟,起一个兜底效果就行了
4.总结陈述
深刻总结
- 扫码登录的本质可以说是将移动端的登录状态同步到PC端
要点牵引
- 这个过程中最重要的是状态传输的流程设计和安全性设计,此处使用Redis状态流转来搞定设备登录状态的同步流程设计,使用设备关联、临时token、过期时间等手段来提升扫码登录的安全性
收尾请教
- 以上便是对扫码登录的设计,如有不成熟的地方还请指教