起
由于公司目前拥有了B/S和C/S两种运营方式,为了方便(模仿)简化用户登录流程,我们可爱的P们就有了扫码登录的需求,并且要求在2周之内搞定上线。于是气急败坏之下决定先把流程梳理一遍,算是一个对比较有趣的需求进行一个记录。
目前的服务结构为:APP->APP服务器->核心系统webApi<–>核心系统<-网页。
因此针对这种结构考虑扫码登录这种容易受到攻击、PV量还大的业务,需要考虑几个安全因素:
- 短时大数次的非正常二维码请求。
- 短时大数次的非正常恶意撞库请求。
- 可能发生的并发同二维码扫码请求。
- 如何嵌入到现有的登录流程中。
- 前端高PV的二维码状态轮询。
承
让我们先来看一下构思好的扫码流程轮询方式的时序图。
将上图定义为:图1. 扫码登录基本时序图。
基本想法很简单,然后是关键的一点,也即如何融合进现有的登录流程。因为扫码登录相当于是受信任的令牌周转,跟用户手动输入用户名密码不同,这个过程中完全没有用户名密码参与。这就需要app在扫码之后,把app端当前登录用户信息写入到某个地方,再使用进行登录。此时,就相当于直接使用用户信息搜索数据库,完成登录的过程,但是没有密码。
看了一下现有的登录流程,都是围绕着密码转的,如果抛开密码,那么现有的复杂的登录流程就要被割裂而实现另一套登录流程。那么在将来维护登录的时候,会增加成本。
最后通过代码追踪综合考量下来,在原有登录逻辑中,增加扫码登录的部分判断分支,由于原登录流程封装的还可以,因此方便扩展以及修改底层逻辑。还算比较喜人,不同的地方在于:
- 跳过验证。
- 跳过密码判断。
- 跳过一些其他的安全性异常检测。
转
接着就需要考虑最开始需要考虑的安全性问题了:
1、短时大数次的非正常二维码请求:此处为web端使用的问题,Ajax部分。使用Redis对IP进行记录,限定时间区间为10秒,如果10秒内的请求次数超过40次,将会被锁定一定时间,定为1分钟。
2、短时大数次的非正常恶意撞库请求:此处为web端轮询的问题,Ajax部分。单次guid的撞库概率低的可怜,但是如果考虑数据库增量不清库的话,可能就真的让别人中大奖了。因此可以考虑再加一层签名,让撞库概率再降低一个n次方。同时也可以在签名验证失败的时候直接返回而不查库或者缓存。
3、可能发生的并发同二维码扫码请求:此处跟web端轮询没有关系,为app端的使用的核心webApi部分。此种情况其实可以考虑接受,因为对于guid令牌的操作只有赋值和置状态。那么在web端轮训到状态改变的时候,就以最后的状态为准。定义状态可以同步推进,但是不能逆向置值。
4、如何嵌入到现有的登录流程中:承中已经描述。
5、前端高PV的二维码状态轮询:此处为web端轮询问题,Ajax部分。出于对二维码轮询可能出现的高PV量,肯定要使用缓存的。考虑也使用Redis作为缓存。
合
于是我们在核心webApi处需要两个接口:
- 扫码访问接口:用于对应guid令牌已经扫码,同时也需要先判断该令牌的状态,不可逆向。直接查询Redis,不存在即认为无效。
- 令牌确认登录接口:用于对应guid令牌置为登录状态,同样不可逆向。直接查询Redis,不存在即认为无效。存在的话就将所需的数据同时也存入到Redis中。
接着我们的web端Ajax处也需要两个接口:
- 获取二维码接口:IP计数限制访问,使用Redis,10秒40次。生成guid令牌以及对应签名,拼接生成二维码返回,并将生成guid入Redis。
- 获取二维码状态接口:先验签,接着使用guid令牌查询Redis。
最后,只有当guid令牌走到最后的登录流程之后,才会正式记录对应guid令牌到数据库中,同时连带状态和所需的所有参数,到这一步的时候,这些数据都应该存在于Redis中,如果不存在,那么就是不合理,应当错误处理。
观察上述,我们可以将guid对于Redis的操作,抽象成一个函数,放入一个业务逻辑类中进行统一维护。当轮询到guid令牌为登录状态时,让前端提交表单,后台接收guid令牌和签名并且优先判断扫码登录,再跳过一些需要跳过的点就成了。
至此为完整的扫码登录轮询方式的流程和设计构想。
结论
早在17年的时候,我从GitHub上看到过一个模拟微信网页版的windowsform项目,拆开来仔细看了看它的登录流程,也是轮询的方式。而且实际在试用的时候,多登陆待扫码就会变得非常卡。但也算是第一次接触到扫码登录方式,算是一种简单暴力的方式。因此高PV的性能问题,是最值得考虑的问题。啊当然,安全第一。
实战
在上述构想和理论的支持下,我们得到了一个基本雏形。真正开发时,还应当考虑一个