任何系统都少不了用户登录模块。一个安全、方便的登录模块可以成为系统与用户交互的良好开端。本文结合笔者的实际工作经验,以及相关知识的学习,旨在对登录模块的一般原理和设计方法进行一个系统性的阐述和总结,希望能够抛砖引玉,进一步深化自己对这一模块的认知。
一、生活中的登录场景
我们日常生活中已经习惯了这样的场景:访问一个系统,系统会要求我们输入用户名、密码(或者验证码等方式),如果正确,我们就登录进入这个系统了。接着系统会显示跟我们有关的信息,比如我们登录淘宝,就可以看到自己的购物车信息、历史购物记录等。
在这个简单的场景里,登录模块做了哪些处理呢?如何确保操作账号的人是账号真正的持有人呢?为什么只能看到自己的购物记录而不能看到别人的呢?要回答这些问题,可以借助 4A 统一安全管理平台解决方案这个框架来帮助我们思考。
二、4A 安全管理框架——系统安全管理的最佳实践
在系统构成的虚拟世界里,用户相关的全部资源都以数字形式存在。登录模块是现实中的人进入虚拟世界的“门户”,其本质目的是实现安全管理,让拥有正确“钥匙”的人“进门”,并阻挡“非法入侵”。1995 年,国际网安界最早提出了 4A ( 认证 Authentication、授权 Authorization、记账 Accounting、审计 Audit ) 的概念,后为国内运营商所发展,形成了 4A ( 账号 Account、认证 Authentication、授权 Authorization、审计 Audit ) 统一安全管理平台解决方案这一最佳实践:
1. 账号
账号可以看作是我们在这个虚拟世界里的一间房子,房子里放着所有我们在这个虚拟世界的资产,比如“身份证”(个人信息)、“存折”(银行卡信息)等。我们希望自己在虚拟世界中的资产得到保护,也希望离开这个虚拟世界后这些资产得到妥善的处置。随着欧盟的《GDPR》,我国的《数据安全法》、《个人信息保护法》等法规的出台,作为系统设计者,不单要考虑账号的注册场景,账号的删除场景也同样重要,一个设计良好的系统需要有完善的账号生命周期管理机制。
2. 认证
认证是门上的锁,没有锁的门别人是可以自由出入的,有了锁就可以确保只有我们自己才有钥匙可以进入。锁的形式多种多样,比如上面例子中提到的用户名 + 密码。锁非常关键,很多黑产的攻击目标就是这个环节,围绕认证的网络安全技术也在不断发展,比如多因素认证、登录加密、异地登录识别、设备更换识别、密码最大错误次数锁定、自动登出、多端登录提醒等。
3. 授权
授权就是定义每个人的房子里装哪些数据资产,我们的“身份证”不会放在别人房子里,别人的“存折”也不会放在我们的房子里,我们对自己房子里的资产拥有控制权限。C 端场景下,通常用户的权限是统一的,有些产品有 VIP 的设定,其权限和普通用户不同,但也是有限的几类,因此权限管理相对简单。B 端系统一般都是为了满足特定的管理目的而存在,管理职责因人而异,无法统一,自然权限管理就相对复杂。在设计 B 端系统前,应该先确定关键用户,了解他们的工作职责,从而对系统应该实现怎样颗粒度的权限管理形成认知。
4. 审计
审计相当于房子里的监控装置。即使锁再好,也不可避免被攻破,房子里装了监控,我们就可以在一些非法入侵事件发生之后去查看当时的情况,挽回损失或者避免损失扩大。所以我们需要对登录、密码修改等敏感操作生成日志,并对这些日志进行定期审阅。
通过上面的分析,我们就知道一个系统的登录模块需要包含的主要功能就是账号管理、认证管理、权限管理、日志管理几部分,下面就来介绍具体如何设计。
三、登录模块的设计要点
1. 账号与密码
设计账号表,最关键的问题是主键的选择(就是以什么作为一个账号的唯一标识)。在一个系统内,用户名、邮箱、手机号都是唯一的,理论上都可以作为主键,但实际上并不好用。
假如把用户名作为主键,下游的业务数据表中可能记录了用户名,例如一个电商平台记录一笔订单的创建人为用户名。这样的话修改用户名的场景就难以实现了,因为用户名一旦更改,用户将无法找回自己以前创建的订单。手机号、邮箱也存在同样的问题。
更好的做法是系统按照某种规则自动生成一串唯一字符作为一个账号的唯一标识,即账号表的主键,我们命名为 UserId。
由于其没有任何业务意义,不存在修改的场景,因此可以保持稳定。用户名、手机号、邮箱等信息作为账号相关的属性,变更的场景就容易实现了。同时需要注意,在开发各类业务场景时,当需要记录操作人身份时,应该统一使用 UserId。
大多数系统都提供了用户名 + 密码这一登录方式,管理密码最主要考虑的是安全问题。密码不可明文存储,否则当数据库被人恶意访问时,所有用户的密码都会被泄露。解决密码存储安全性问题,比较常用的方式是加盐哈希。
满足创建账号、设置密码、用户名 + 密码登录这些基本功能,账号表至少需要包含以下字段:
注:本文提到的表单字段仅为业务逻辑类字段,并非数据库建表实际字段。开发过程中一般都需要添加代码逻辑所必须的字段,如逻辑主键、创建时间、创建人、更新时间、更新人等,上文提及的加盐哈希需要额外存储密码盐值,为了保持登录会话可能需要添加字段保存 token 等,这些用于技术实现所必须的字段不在本文的讨论范围内。
用户注册、密码登录流程示例如下:
2. 手机号 / 邮箱登录
对于用户来说,记忆密码是不方便的,而且也存在安全隐患。手机号 + 验证码登录操作简单,用户接受度高,是国内普遍采用的注册、登录方式。在海外市场中,邮箱 + 验证码也是很常见的。为了实现手机号 / 邮箱的绑定 / 换绑、验证码登录功能,我们需要扩展账号表字段,至少需要添加手机号、邮箱 2 个字段:
手机号 / 邮箱的绑定 / 换绑、验证码登录流程基本相同,可以合并表示,具体见下图:
3. 第三方登录
为了简化用户注册流程,降低用户的注册成本,不少应用都提供了第三方登录方式,国内常见的有通过微信、QQ、微博等账号登录,海外常见的有通过 Google、Facebook、Twitter 等账号登录。例如下面这个登录页,底部提供了 3 种第三方登录方式:
第三方登录使用的主流授权流程是 OAuth,各大第三方平台的开发者文档都有接入方式的详细说明。为了实现第三方登录,我们需要进一步扩展账号表字段。考虑到第三方平台有多个,可能会陆续接入,为了方便系统扩展,应该添加第三方平台类型字段,表示登录来源。用户通过第三方平台登录后,第三方平台会返回表示用户身份的唯一 ID,我们需要将这个 ID 和自建系统的 UserId 进行关联,因此也需要一个字段记录。具体如下:
由于登录过程引入了第三方平台,登录流程更加复杂了,可以参考下面这个时序图来理解整个第三方登录的过程:
4. 权限管理
用户成功登录系统后,接下来就需要知道这个用户可以访问哪些资源、可以执行哪些功能。例如在一个组织的 HR 系统内,每个人只能看到本人及下属的考勤记录,这就是一种对数据的权限控制;又如系统管理员可以修改员工信息,普通用户只能查看员工信息,这就是一种对操作的权限控制。
在访问控制领域,广泛采用的权限模型有 RBAC(基于角色的访问控制)和 ABAC(基于属性的访问控制),二者也可以结合使用。RBAC 是通过把一组权限封装在一个角色中,再把角色授予用户,以此来实现灵活的权限控制。
RBAC 的优点是操作简单,并能满足大部分场景下的访问控制要求。在 RBAC 模型中,用户与角色是一对多的关系。ABAC 的控制规则是建立在一组“属性”上的。比如可以定义一个规则,仅允许人力资源部门的员工在 9:30 至 18:30 之间查看员工简历,“人力资源部的员工”是用户属性,“ 9:30 至 18:30 ”是环境属性。ABAC 的优点是具有更多维度的控制变量,从而实现叫 RBAC 颗粒度更细的访问控制,但建立规则的过程也更加复杂。
本文以 RBAC 为例。在 RBAC 模型中,一个角色内包括多个权限项,一个用户可以有多个角色,结构如下图:
我们至少需要增加 4 张表来表示上述结构:
首先是用户角色表(user_role),用来表示用户和角色的关联关系,通过 UserId 与账号表(user_account)进行外键链接,用户登录后,可以在此表查找其拥有的角色。
然后是角色权限表(role_permission),用来表示角色和权限的关联关系,通过 RoleId 与用户角色表(user_role)进行外键链接,找到用户拥有的角色后,可以在此表查找这些角色内包含的权限。
另外还需要角色表(role_info)和权限表(permission_info),用于记录角色和权限的基本信息。
整体的数据结构如下:
5. 日志
在内部控制领域,控制分为预防性控制、检查性控制、纠正性控制 3 类。账号管理、认证管理、权限管理都属于预防性控制,即通过事先建立这些机制,可以保护用户数字资产的安全。而日志管理则是一种检查性控制,是一种事后的控制。当我们需要排查、诊断系统问题,追溯入侵事件时,有日志的帮助,可以大大提高分析效率。
对于登录模块,最基础的日志需要记录账号、登录时间、登录 IP、登录设备等信息。登录后的操作日志,则需要根据不同的业务场景设计。为了平衡成本和收益,通常需要对敏感操作生成日志,而不是事无巨细地记录。比如某个表单中含有关键的数据,可以对这个表单中的新增、删除、编辑操作生成日志,编辑类操作还可以进一步记录编辑前、编辑后的信息。
6. 延伸思考
一般情况下,用户注册账号之后,系统还需要收集用户个人信息,例如头像、性别、生日、兴趣爱好、从事的行业、教育背景等,这些信息是否适合记录在账号表中呢?出于系统扩展性考虑以及解耦的思想,笔者认为另建一个用户信息表存储这些信息更好,虽然它和账号表是一对一的关系。账号表只负责账号、登录、认证功能。当然这个问题没有标准答案,合理即可。