安全设计无小事
安全设计是任何系统必须要考虑的问题,也是架构设计中最重要的部分。一个系统一旦出现安全性问题,往往都是致命性的,企业需要承受巨额的经济损失,以及无法挽回的其他间接影响,甚至企业倒闭。
攻击案例:一次短信攻击让国内某知名企业瞬间损失了八十余万元,间接损失无法估计。
很多网站都有使用手机号发送验证码的功能,攻击者通过浏览器的开发者模式,或者网络抓包的方式获取到其发送短信的接口地址和请求参数。然后使用HTTP工具,或者编写程序进行模拟发送,即可完成攻击。
如图5-1所示,某家公司官网的注册功能的页面,在Chrome浏览器下,按“F12”键进入开发者模式,然后在登录页面填写信息后,点击 “发送验证码”按钮。这时分别可以看到发送短信的请求地址和接口信息,以及请求参数信息,其中有一项就是手机号参数(mobile)。
图5-1 浏览器开发者模式
有了这些信息,就可以模拟请求信息,写一个死循环去发送验证码短信了,并且可以随意更换手机号。
如果这家公司的系统没有做过任何安全控制,那么将带来以下严重后果。
(1) 巨额的经济损失。如果一条短信按照1角钱计算,一小时发送100万条,就是10万元,如果持续24小时就是240万元,这还是保守估计,如果采用高并发方式发送,那么损失会更加惨重。
(2) 如果随机设置用户手机号进行发送,则数以百万、千万的人都会收到带有公司标签的短信(这是运营商的强制规定,如短信前面或后面都会有【×××公司】、【×××网】的标签)。那么,公司的声誉会受到极大的损害,不但面临大量的用户投诉,造成的声誉损失也是无法估计的。
类似这样的例子还有很多,如用户的密码暴力破解、用户信息被盗用、恶意注册等。正因为存在这些问题,才需要对安全设计足够的重视。
主动与被动登录踢出设计
登录踢出可以作为一种保护措施,在很多情况下都需要进行这样的设计。例如,用户在网吧登录某个系统却忘记了退出,这就十分危险。其他人不用登录就可以直接访问该账号,不只个人信息会泄露,甚至对方可以进行一些具有破坏性的操作。
因此,系统不可以保持永久的登录状态,除用户自己主动退出外,还必须具有踢出机制。
注意
退出和踢出的含义是不同的,退出是指用户正常退出,而踢出是一种系统行为,用户无法阻止。
在以下几种场景下需要登录踢出设计。
(1) Session过期:在B/S模式下,最常见的就是用户登录系统后长时间不操作,为了保证用户安全,则会由于Session过期而被踢出。
(2) 不允许多设备登录:有些App是不允许多端、多设备同时登录的,如微信,在A手机登录后,再使用B手机登录相同账号,则A手机会被自动踢出。
(3) 权限控制:一般在企业内部系统使用,有一些系统用户的功能权限是在登录时加载的,如果管理员修改了此用户的权限,则用户必须退出后重新登录。
(4) 升级维护:在一些系统升级之前会将用户主动踢出,并且不允许登录,保障系统发布过程中不被访问。登录踢出设计有主动踢出和被动踢出两种设计方式。
1.主动踢出设计
不允许多设备登录、权限控制和升级维护的场景,都可以使用主动踢出的方式来解决。对于App端登录踢出,常用的架构设计如图5-2 所示。
图5-2 主动踢出流程
(1) 设备A登录时,请求后端服务器。
(2) 服务端查看用户是否存在其他设备已经登录,如果存在,则推送消息给个推服务。
(3) 个推服务会将通知消息推送给设备B。
(4) 设备B接收到消息后,直接将用户踢出到登录页面,并提示
“您的账户已经在其他设备登录”。
注意
个推服务的作用是由服务端向用户终端(手机App、计算机、电视等)推送消息,如提醒信息、活动信息等,一般以JSON格式进行发送,App端需要集成相应的SDK。 个推技术参见8.4 节。思考1:图5-2所示的架构是否存在问题?如果公司有多款产品,它们都有个推和透传的需求,那么每个服务都要集成一遍个推功能,无法达到服务复用。
因此,需要将消息推送服务抽象出来,成为消息中心独立服务,以此来达到服务复用,架构演变如图5-3所示。
图5-3 消息推送服务架构
在图5-3所示的架构下,多个系统均将消息发送给消息中心,由消息中心与个推服务进行交互。消息中心可以被所有子系统所使用,从而降低了系统的耦合度,提高了复用性。再进一步,消息服务可以封装自己的客户端SDK,让调用方集成更加方便快捷。
思考2:图5-3所示的系统设计是否具有通用性?如果公司的系统是桌面软件(如企业内的某些财务软件、医院的门诊管理软件、游戏软件),并不是手机App怎么办?
如图5-4所示,桌面软件消息推送架构并没有实质性变化,只是不需要依赖于个推系统提供的功能了,而是由消息中心直接把踢出消息推送给终端软件。桌面软件需要开启监听,服务端发送TCP/UDP消息通知,而通知的内容可以随意定义,可以是JSON、XML等。客户端进程收到通知后,将用户踢出到登录页面,并提示“您的账号已经在其他设备登录”即可。
图5-4 桌面软件消息推送架构
可以发现,消息中心能做的事情不只是登录踢出,各种活动通知、福利发放、锁定账号等由服务端主动发起的事情都可以实现。消息机制可以提高用户黏性,提高App或系统被激活的频率,从而提高系统的日活和使用量,详情可参见第8章。
思考3:手机App端、PC端桌面软件都可以实现主动踢出,那么
Web端是否可以实现呢?
Web端使用轮询机制,或者通过WebSocket技术进行消息推送即可实现。
总之,主动踢出模式都是采用主动推送、被动监听的方式来实现,只是采用的技术和协议不同而已。
2.被动踢出设计
Session过期的场景属于典型的被动踢出设计,必须由客户端主动发起请求,服务端才会去检测用户是否依然在保持会话。不允许多设备登录、权限控制和升级维护的场景,除了使用主动踢出的方式,也可以使用被动踢出的方式来实现。
(1)Session模式被动踢出。
Session模式被动踢出流程如图5-5所示。
① 客户端发送任意请求(需要登录后才有权限的请求)到服务端。
② 服务端获取客户端的session_id,在内存中查询用户Session是否存在并且有效。
③ 如果Session已经失效,则返回错误,或者重定向到登录页面,从而完成踢出。
图5-5 Session模式被动踢出流程
(2) Token模式被动踢出。
Token模式下的踢出与Session模式并没有本质的区别,只是第二步有点区别。如图5-6所示,后端服务接收到前端请求后,需要先去
Redis、Memcached等缓存中间件或数据库中获取Token,然后再验证其是否存在并且有效。
图5-6 Token模式被动踢出流程
(3) 多设备登录模式被动踢出。
多设备登录也可以进行被动踢出,主要是借助Redis等缓存来实现,流程如图5-7所示。
① 设备A向服务端发起登录请求。
② 服务端将设备ID、用户ID作为Key,将用户信息存储到Redis等缓存中。
③ 设备B使用相同账号登录,向服务端发起请求。
④ 服务端查询该账号是否存在其他设备已经登录,如果存在,则将其他设备的Redis记录清除。
⑤ 当设备A再发起任意请求时,服务端检测到用户设备没有Redis 记录,则会返回错误,踢出到登录页面。
这种方式存在的问题是用户体验较差,但是稳定性较好。主动踢出模式虽然用户体验好,但是很容易因为消息推送失败,而导致无法踢出其他登录设备,造成安全隐患。
图5-7 多设备登录模式被动踢出流程