搭建账户系统

Building account systems

Troy Hunt 近期发表了一篇题为『新时代的认证指南』的博文。文章对于「在你的网站中应该使用什么样的密码规则」给予了诸多建议,而这些参考了正式的政策提议的建议常常很能帮助你去说服你的同事或老板。

我在 Google 工作期间所从事的一个项目就是他们的统一账户系统(特别是反劫持)。大多数网站都会有一个登录系统,阅读 Troy 的文章极大地启发了我去建立这样的一个系统,从而将那些建议应用其中。

1. 最好不要有

不管是什么业务,进行用户认证并不是你的主职,一个现代的登录系统要考虑的方面有很多,密码仅仅只是一个开始。如果你成功建立账户,最终你还得考虑:

  • 对忘记密码的恢复
  • 电子邮箱的认证
  • 账户的登出,常常比你想象的要困难(见下文)
  • 密码的强力保护
  • 基于短信、手机应用和硬件密钥的双因子验证
  • 对账户劫持的保护(当攻击者已经知道了正确密码而用户还没有双因子验证时)
  • 用户的地区、语言、姓名、个人头像等的偏好
  • 对桌面和移动端的登录支持
  • 异常行为的通知
  • 只允许特定手机的登录

随着大公司提高用户期望,以及攻击者的技术在不断地进步,努力跟上潮流已经变得不切实际。幸运的是,你现在可以将你的身份验证环节外包给那些支持 OAuth 协议的公司。

Web 开发者常常会在建立完自己的账户系统之后,才觉得添加『使用 Facebook 登录』或是『使用 Google 登录』是个不错的方案。如果你在阅读这篇文章时正好在建立一个全新的网站,我建议『使用…登录』应该成为你的网站的唯一选项。如今,建立一个自己的账户系统,就意味着要建立一个数据中心,以此代替 AWS。这样付出的代价是巨大的。

人们有时候会担心,如果他们只提供『使用…登录』按钮用来登录,那么那些大型的 ID 提供商可能会试图窃取他们的客户。通常的迹象表明,人们担心的情况是,使用 ID 提供商登录,但是却被要求设置密码。其实不用担心这一点,这种情况不太可能发生,就算真的发生了,你也随时可以通过电子邮件给他们发送一个链接将你的客户群迁移到你自己的系统上。

2. 使用电子邮件或电话号码来识别不同用户

不要强制用户设置用户名,即使你的业务体验是基于用户名的,比如聊天论坛之类的。用户通常是通过邮箱或者是电话抑或是两者同时验证的,如果你想让每个用户都拥有一个独一无二的用户名(用来展示的),那应该单独选择,为什么呢?

  • 无论如何你都是要用户提供邮箱地址的
  • 如果用户名成为你的系统的个人识别符,你要考虑用户是会随时更改它的
  • 用户经常会忘记用户名,可一般不会忘记邮箱或电话号码
  • 挑选用户名的过程常常是让人沮丧,一旦用户使用了一个用户名,却因为该用户名已经存在而不能通过时,有些人就会放弃了,从而你就流失了一位用户
  • 将用户名和用来展示的名字分开可以大大减少对用户设置的限制,例如禁止空格

3. 完全放弃密码

如果你还没准备好将账户系统完全依赖于第三方 ID 提供商,那么千万不要设置密码,这样对每个人都有好处。

这个主意并不像它听起来那么愚蠢。你已经向用户询问他们的电子邮箱了,你所应该在你的登录系统上添加的第一个功能就是用户忘记密码后该如何恢复,你可以通过电子邮件给用户发送一个可点击的链接。这样,只要用户能够使用该邮箱就能够登录你的系统,而你的网站密码也用不着增加额外的安全性。

我们跳过这一步,直接进入下一步,取而代之的——你的登录系统可以变得更简单,只需通过邮件向用户发送一封包含了登录cookie的链接,用户只需点击链接便能登录。Medium.com 便是如此

通过这种方法,只要用户登录你的任何一个带有电子邮件客户端的服务器都能生效。对于台式机、笔记本电脑、手机和平板来说也是如此。对于游戏机和电视来说可能行不通,不过你的目标用户可能不是这些人群。其中的匹配过程最好用蓝牙的方式,因为这些设备没有方便使用的键盘。

过去常常有这样的说法:缺少密码输入框会使用户感到不自在。而现代的谷歌登录体验正是如此,他们仅仅要求用户输入他们的电子邮箱地址,所以用户并不会感到有什么不自在,而且这么做还大有好处。

而这种方法还有个好处:有些人只有电话号码而没有电子邮箱。在发展中国家尤其如此,所以如果这些国家的用户是你的网站的潜在目标市场的话,最终你可能要支持仅能通过手机接受验证码的方式登录。这样的账户根本就不需要密码,如果你的所有的用户都有密码,那么你需要返回并为安全敏感的代码路径添加许多特殊的情况(这很容易导致致命的错误)。

4. 不要使用密保问题

如果你就是想使用密码——大概你懒得向你的老板解释为什么你这么特立独行,那么请至少不要让用户通过密保问题来找回他们的密码。

  • 密保问题常常被猜测。用户很难想出那些只有他们知道而其他人答不出来的问题。
  • 预设的问题使得猜测的现象更加严重
  • 预设问题往往带有文化差异,从而使得它们对于许多用户并不友好(例如:『你们高中学校的吉祥物是什么?』)。
  • 一些『精明』的用户意识到他们不能想出一个难以猜测的答案,所以仅仅在这个位置填入密码,导致了他们在忘记密码时无法恢复。
  • 还有一堆的高端黑客,会在密码恢复流程上做点文章,你可不希望这事放生在你身上吧。

Google 曾经在密保问题上遇到过严重的麻烦。这是我的几位老同事发表的研究,值得一看(视频在下面,来自youtube,需要翻墙)。

一场在谷歌进行的关于密保问题和答案的谈话

这是一些问答的例子:

  • 问:你最喜欢的食物是?答:披萨。答案常常是披萨。仅仅通过猜出这个问题的答案,你就能破解大约 20% 的说英语的用户。再添加十个猜测选项,你就能破解三分之一的设置了这个问题的用户。而对于韩国用户,你可以用10个以内的选项破解 43% 的用户。
  • 问:你是在星期几结的婚?答:星期四 是用户自定义的问题,但却有个致命的缺陷 —— 一般攻击者只需要尝试 5 次,就能破解正确答案。而这还不至于被检测为暴力破解。
  • 问:我是在哪个城市出生的?答:首尔在一些国家里,大多数人会聚居在少数的几个大城市里。观察 ID 验证用户界面所使用的语言,可大大缩小可能的城市列表。通过这个问题,你能够破解 40%的用户。
  • 问:我的第一位老师叫什么名字?答:Mr Smith, Smith, John, John S. Smith,JOHN SMITH, Jon Smth。 这些都是正确答案,但是却不能正确通过。我为这些问题提供了模糊匹配的模式,因为用户的答案总是差那么一点儿。匹配逻辑通常需要了解一些问题背景(『编辑距离』算法本身不足以满足诸如街道地址等的情况)。你还得让你的产品支持多语言,祝你好运吧。

专业的账户系统是不会单独使用密保问题来允许用户恢复密码的。它仅仅只是一种参考。我只给予你小于 2% 的机会去通过一个足够复杂的机制来获得这个权利。这就是为什么 Google 逐步淘汰密保问题而采用短信的方式来恢复密码。当然短信恢复自身也存在一些问题,但相比于密保问题还是好很多的。

5. 避免使用验证码

验证码是许多登录表单的常见功能。对于它,我在 Google 也投入了大量的工作。但是,在如今验证码几乎没有什么价值,而且执行率非常之低。

这些验证码都无济于事。

首先要正确理解验证码的作用,它们仅仅在对自动化攻击施加非常基本的限制上能起到作用。他们并不会保护你的账户系统免于批量注册的风险。除了账户安全,我还花了几年时间研究 Google 的注册滥用。我们亲眼看到垃圾邮件发送者轻松地解决了数千万个那些我们认为很难的验证码。有那些专业处理验证码的公司,如 DeathByCaptcha,他们使用的是光学字符识别和人工识别。普通的验证码让盲人用户无法进行注册,这确实是个问题。而基于语音的验证识别对于机器很容易,对人来说却很困难。

使用验证码阻止暴力破解密码是很有帮助的。暴力破解一个账户的密码,可能需要成百上千次的尝试,一个简单的方法来阻止这种现象是在他们经过了几次失败的尝试之后就开始加入验证码。在机械的循环中,即使是使用一个简单的验证码来延缓进程也足够了。

对于阻止批量账户注册,验证码却不太管用。建立一个系统去检测和阻止这类现象是另一桩工作,在这方面我也花了好几年的时间。你可以大致了解一下这有多困难,登录 buyaccs.com 并对比一下地下账户销售商收取的巨大差价。防御系统较好的网站的账户通常会收取更高的价格。除非你是 Big 5 之一,不然在注册安全方面,你所做的不可能超过我们,这也是我建议你将登录系统外包给那几家主要的公司的另一个原因。

如果你仍然想要使用验证码,请使用reCAPTCHA并确保你的验证码放置在了适当的位置,以免重放攻击。不要尝试使用你自己制作的或是你在Github上找到的工具包,这样的验证码很容易被现代的光学字符识别所解决,除了降低客户注册的成功率之外并没有什么用处。

6. 外包双因子验证

如今双因子验证是一个很常见的功能。然而,把它做好却是很困难的,而且花费不菲,你不会想自己动手去实现它的。

  • 短信是不稳定的,特别是在有些国家,恢复码常常不能显示。你最终可能会选择语音合成的电话,因为电话更加稳定一些,而现在你又需要考虑多语种的语音合成引擎了。
  • 大量的短信或电话将会是一笔大的开销,即使你能通过大批量获得优惠。
  • 人们可能常常会更换电话号码。如果你的密码恢复流程是基于电子邮件地址的,那么这个过程将会变得很容易。但是一旦你的系统引入了稳定的双因子认证,密码恢复将变成你系统中的一个漏洞。如果你不去修复它,攻击者将会很轻松地进行破解。而如果你尝试阻止它也并不会起作用。
  • 双因子验证会被攻击者滥用,他们将它添加到钓鱼或者黑入的账户里。这是为了在执行恶意活动期间,防止真正的用户取回该账号。
  • 电话号码很容易受到移植攻击,所以如今的趋势是要求用户设置移动应用或安全密钥。为了实现这项措施意味着更多的工作,当然,这两项可能都不管用,所以你最终还是需要一些客户支持流程来帮助他们恢复。
  • 如你所见,双因子验证增加了大量客户的手动操作,因为你不再使用密保问题或电子邮件的方式恢复用户的密码了。而这个开销很大。

其中一些问题是很根本的,但是大多数已经有人帮你解决了,他们将免费为你支付电话费和客户支持人员

不过,如果你仍然不想使用他们提供的服务,那么还有一些创业公司可以为你解决小部分的双因子验证难题。

7. 不要强制用户更改密码

Troy 已经把这一点解释的很好了,我这里就不再赘述,但还要再强调一遍,这很重要。不要仅仅是因为用户的密码已经使用一段时间了,就让用户更改密码。

  • 一些用户可能无法通过这个流程,从而导致你流失一部分用户。
  • 用户可比你聪明,他们会更改密码(一次、两次、三次),然后立即将其更改为旧密码,这意味着你得存储最近密码的历史记录以防止此类行为。但我敢打赌,你不会这样做的。
  • 这样并不会增加安全性。

8. 不要为会话设置有效期

是的,这又是个不好的『最佳实践』。人们常常会为会话 cookie 设置有效期,觉得这样做增加了安全性,出于同样的原因,人们会认为为密码设置有效期也会增加安全性。

  • 攻击者往往会立即进行恶意活动,所以设置有效期并没有多大用处。
  • 会话有效期这一设置使得用户习惯于意料之外的密码提示,这使得他们非常容易被欺骗。
  • 存储有效期的随机性会产生大量的 bug,导致了你的开发人员将大量的时间花在了修复 bug 上。你的网站的大部分代码应该不能处理这种,在一个操作中途,使用的会话过期了的情况,所以你必须返回去修复它,前提是你能够发现的话。而由于用户报告的随机性,这使得追踪错误变得更加困难了。

9. 记得登出

在不成熟的账户系统中,登出错误是非常常见的。这听起来很简单,但是实现这一功能的最明显的方法,是有缺陷的。

  • 简单地删除会话的 cookie 对用户来说是方便的,但是这意味着你在遭受『跨站脚本攻击』后无法恢复。一旦发现『跨站脚本攻击』,你会希望,让可能被盗取的会话 cookie 无效,但是如果登出只是『要求浏览器删除 cookie』,那么这样做是不行的。
  • 将时间戳添加到会话 cookie,然后设置『最后登出时间』,每个操作都需要检查帐户数据库,以了解用户的会话是否过旧。这可能会导致操作响应变慢,意味着开发人员将要对此进行优化(毕竟这似乎也没什么)。但是如果他们移除了对攻击者感兴趣的一个端口的检查,那么你在第一步中遇到问题将会再次出现。另外,这意味着退出一个浏览器或设备,就会将所有用户登出,这不是预期的行为。

正确的方法是使用内存中缓存来保存过期会话 cookie 的列表。但是,对于大多数公司来说,有个成本更低而且足够好的替代方案:让用户的退出链接仅仅当作是清除会话 cookie 的一种方式,紧接着可以让会话 cookie 过期,并且每隔5分钟自动更新。替换过期会话 cookie 的行为可以通过查询数据库,以查看管理员是否强制注销了该账户。如果用户显示的是过期的 cookie,则需要重新登录。这就意味着 cookie 清理之后就不太可能被盗用了。

10. 从营销邮件中分离帐号电子邮件

一般我们会用公司的主要电子邮箱服务器来给用户发送恢复密码链接、登录验证等信息。然而,贵公司的一些人却会通过给用户发送那些他们不想收到的商业邮件与用户建立『联系』。

即使用户同意在帐号注册期间收到这类信息,但其中大部分用户却不想再收到这样的信息,有些人甚至会将其举报为垃圾邮件。那些精明的用户知道,这是一个极其方便的解决方案,仅仅简单地点击『举报垃圾邮件』,就能让这些令人讨厌的电子邮件消失,而不必把精力花费在寻找用微小字体写着的『取消订阅』链接,或是劳神费力地写电子邮件过滤器。

而不幸的是,这类行为将会降低你的电子邮件域名的信誉。从你的帐户系统发送的邮件最终很可能会进入用户的垃圾邮件文件夹。我们在注册或进行密码恢复的流程中,都能看到这类让我们检查垃圾邮件文件夹的提示 —— 就是这个原因。

解决这个问题的一种方法是,购买单独的顶级域名来发送邮件,并确保符合电子邮件验证标准。但是,一些用户可能会注意到域名不匹配,从而将你的电子邮件举报为网络钓鱼。最佳方案是使用不同电子邮件验证标准的域名发送你的营销邮件,但你的产品人员可能不认可。所以,还是那句话,你选择自己干的那一刻,也同时承担了痛苦。

11. 保护好你的密码数据库

一旦你拥有了密码,你的数据库就成了攻击者的目标(而且他们常常能得手)。他们对你的公司并不感兴趣,他们只是想要密码,以方便他们尝试那些更高价值目标。所以,数据泄露是个严重的问题,也许对客户的直接影响没那么大,但有可能导致严重的后果。而使用 OAuth 协议的数据库对于攻击者来说却没什么价值,因此这类用户就比较不会被攻击。

结论

关于帐户系统我还能写好多东西。保护你的网站,使其免受恶意帐户入侵或注册,这方面内容可以单独写成一本书了。这书我写不了,不过如果你有兴趣的话,可以看看这个视频,这是我在 2012 年的一次谈话

老实说,这看似是一个浩大的工程,实则并非如此。所以我一直建议你要硬着头皮坚持做下去,并且把你的账户管理外包给那些大公司。因为,你的主要业务并不是去操心怎样摆弄验证码、不是怎样写『登出』的设计文档、不是诊断为什么你会流失那些忘记密码的用户、也不是去考虑为什么发送信息到秘鲁会不稳定。你在这些事情上花费的每一分钱,对于那些提供了『使用…登录』的竞争对手来说,他们将这些钱都花在了他们的核心业务上。

所以,不要回头看了,放弃你的密码数据库吧。