关于本系列

本文是笔者计划的系列《自建折腾记》的第三篇。此系列记录了笔者在自己刚租的服务器上折腾三个自搭建(self-hosting)服务的过程。但是,如果看列表会发现这个系列从 1/3 直接跳到了 3/3, 这是因为作为 2/3 的 Matrix 服务器已经驾鹤西去了(既因为一些“大人的原因”,又因为 Dendrite 服务器实在是太难用了)。剩下的服务分别是分布式短文社交平台 Misskey 与密码管理器 Bitwarden(实际上用的 Vaultwarden 作为服务端)。

背景

笔者曾经的密码管理可谓是把所有不能犯的错误都犯了——弱密码、所有网站/服务用同一个密码、密码与生日等相关等等。最后,在 000webhost 爆出明文密码泄露后,笔者终于神功大成,实现了裸奔上网——这是笔者第一次被迫改几十个网站的密码。

然而,显然那次并没有吃够教训,只是改了一个稍强的密码,但仍然所有网站共用。因此在某年(忘了哪年),笔者再次神功大成,实现又一次网络裸奔,区别是这次要改的密码数量跑上了100。

在第二次泄露之后,笔者决定彻底改变现状。显然,每个网站/服务需要设置不同的密码,且最好是越随机越好(或者说,熵越大越好)。一个一个记忆如此多的密码是不现实的,而以前杂志上看到的一个方法是通过一个根密码+各个网站的名字,在脑子里计算出一个密码(如根密码是abcdefg,网站域名是 google,则得一个密码 agbocodgelfeg)。然而这样仍有被社工的风险,同时每次输密码来这么算一下也太花脑力了。

而另一条路径是每个网站使用随机生成的密码(足够的熵),并使用密码管理器记忆、自动输入。原先一直不这样做是因为没有找到合适的密码管理器方案:“古时候”浏览器自带的密码管理器直接存在本地,在手机/其它人电脑上没法访问,而 Yubikey 等纯硬件方案有丢失风险;“新时代”浏览器的密码管理器使用 Google 账户甚至国内服务商的账户同步,我十分不放心。即使是 LastPass、1Password 等声称使用端到端加密的方案,也会有数据存在云厂商管理的数据库上,且部分公司曾爆出来泄漏事故。

最后,笔者先后找到了两个可完全自己掌握数据的密码管理方案:KeePass 与 BitWarden。其主要原理就是首先设置一个足够长的“主密码”,且用户需要记住这个主密码。通过这个主密码加密整个密码库其它部分(事实上是由主密码派生出一系列对称和非对称密钥),实现在服务器不知道明文密码的情况下,用户可取出明文密码输入到网站。

KeePass:纯本地方案的尝试

笔者在前些年使用的方案是 KeePass 2。KeePass 是一个 Windows-only 的本地开源密码管理器,且基本没有自动填充功能(提供了一个全局快捷键通过键盘模拟的方式输入密码)。同步使用 WebDAV 进行,而自动填充一般使用非官方浏览器插件 Kee 进行,手机端提供了 Keepass2android 等应用实现同步与自动填充。

KeePass 大概用了 2~3 年,过程中笔者遇到了以下几个问题:

  • 定期同步总是抽风,常常需要手动同步(出门时手机上发现电脑上的某个密码没有同步过来是十分绝望的)
  • Kee 插件使用体验不佳,常常填充不上、不自动识别填充,或无法保存(事实上 Bitwarden 的更难用,等会会提到)
  • Keepass2android 常常被杀后台,一旦被杀就要重新输整个(20+位)主密码

因此,笔者在友人的推荐下了解了 Bitwarden。

Bitwarden 与 Vaultwarden

Bitwarden 为一个开源/SaaS 并行的密码管理器项目。其主数据库存在云端,各客户端通过 HTTP REST API 从服务器上同步库,而不是像 KeePass 那样以一台电脑上的为主数据库,这台电脑把主库通过 WebDAV 同步到云存储后其它端再去同步。另外其还提供了一个网页界面,这样在其它人的完全没有安装客户端的电脑上也能临时取出一个密码进行登录。

Bitwarden 在其安全白皮书中描述了其如何保证用户的密码库安全。主要是通过主密码与邮箱派生出一个 Master Key,利用这一个 Master Key 的 Hash 与服务器进行鉴权(相当于事实上对于服务器上的这个账户而言,你的密码是这个 Master Key 的 Hash,而非主密码或 Master Key 本身——后两者从来不会发到服务器),同时利用这一个 Master Key 保护一个对称密钥。加密后的对称密钥与利用此密钥加密过的密码库存放在服务器上。由于服务器没有 Master Key,无法拿到这个对称密钥,也就无法看到明文密码库。

然而我并不想用 Bitwarden 的官方服务器,也不想自建 Bitwarden 实例,因为官方实现比较吃资源,且笔者也用不到如此丰富的功能,如 OIDC 等。但是,社区有人维护了一个非官方 Bitwarden 服务器项目——Vaultwarden。其使用 Rust 编写,比较节省资源。

服务器安装

本体安装

这次笔者同样使用了 Docker Compose 进行容器的编排。这部分的官方教程:https://github.com/dani-garcia/vaultwarden/wiki/Using-Docker-Compose

直接上 docker-compose.yml 文件内容:

version: "3.6"
services:
  vaultwarden:
    image: vaultwarden/server:latest
    restart: always
    volumes:
      - "./data:/data/"
    ports:
      - "127.0.0.1:9001:80"
      - "127.0.0.1:9002:3012"
    environment:
      - SIGNUPS_ALLOWED=true
      - WEBSOCKET_ENABLED=true
      - DOMAIN=https://分配给vaultwarden的域名

这里有几点注意:

  • 数据库用的默认的 SQLite 实现,挂载的是当前文件夹的 data 子文件夹,需要提前创建好并调整好权限
  • 我去掉了官网里的 Caddy 配置,而是把 80 和 3012 端口(分别是 HTTP 和 WebSocket)映射到了宿主机的 9001 和 9002 上(通过指定绑定地址为 127.0.0.1 来禁止外网连接),因为我的 Caddy 是放在宿主机上统一管理各站的
  • 注意这是最开始的文件,在我们注册好自己的用户后,还要倒回来关用户注册。

本体没有其它需要配置的(如 .env),也不需要 build,直接 docker compose up 看到没有问题,即可 Ctrl+C 后 docker compose start

反向代理配置

在 Vaultwarden 这种安全至关重要的应用上,笔者强烈建议使用 HTTPS。官网的配置也全部包括 HTTPS 的配置。笔者仍然是使用宿主机上的 Caddy 提供反向代理与 SSL 功能,往 Caddyfile 中追加如下内容:

分配给vaultwarden的域名 {
        handle {
                reverse_proxy 127.0.0.1:9001
        }

        handle /notifications/hub {
                reverse_proxy 127.0.0.1:9002
        }
}

注意域名要和上面 docker-compose.yml 中的一致。追加后重载 Caddy 配置,即可通过浏览器访问你的 Vaultwarden 实例地址。

用户注册与数据导入

进入实例后,点击“创建用户”,创建自己的用户,并进入密码库。

由于笔者已经导入了数据,所以密码库里有密码。如果你在此之前也用过其它密码管理工具(如 KeePass、Chrome 自带管理器等),可以点击上方的工具,并在左侧切换到“导入数据”。选择导入格式后会弹出获得此格式文件的教程,照做即可。

同时笔者在全部密码导入配置好后,也导出了一份加密后的密码库文件,以便不时之需。

在用户注册配置好后,由于此实例为单人实例,我们需要关闭新用户的注册。将 docker-compose.ymlSIGNUP_ALLOWED 后的 true 改为 false,再 docker compose down && docker compose up 即可。

客户端配置与使用

Vaultwarden 作为非官方服务端,近乎完全兼容 Bitwarden 的客户端协议,因此直接使用 Bitwarden 的客户端就可以了,笔者安装的是 Windows 与 Android 平台的客户端程序,以及 Chrome 里的插件。安装后三者提供了十分相似的界面:选择自建实例,输入服务器的地址,再使用邮箱和主密码登录即可。

Windows 和 Android 上的应用程序可以配置为使用指纹快速解锁密码库,同时 Chrome 插件可以连接到 Windows 客户端,利用 Windows 客户端为中转实现指纹解锁,但考虑到安全性笔者基本没有打开这些功能,仅仅保留了 Android 上的指纹解锁(因为手机上每次输快 30 位的密码实在太难受了,PC 上还行)。同时还可以设置超时自动锁定密码库、自动同步等。

Android 上为了使用自动填充,我们还需要打开 Bitwarden 客户端的自动填充权限——在“设置”中选择最上面的“自动填充服务”,按照指引配置即可。

配置好客户端后,我们自然要讲讲密码管理器的两大关键功能——自动创建条目与自动填充。

Bitwarden 的自动条目创建只能用灾难来形容。笔者前面提到了 Kee 插件在这功能上做得有些糟糕,一般是在检查到网页提交密码后,在 Chrome 插件栏上高亮自身,用户点击后再提示是否新建或更新条目。而 Bitwarden 插件正常情况下会在提交了密码后,在网页上方弹出一个通知条,问你是否新建更新条目。问题就在于,这个条大部分需要它的时候它并弹不出来,倒是有的时候不需要(比如已经创建过了,直接按原密码登录时)会弹出来。另外这个条是直接通过 DOM 渲染在网页上的,因此遇到部分全屏的 Web APP 会把画面顶到下面去。笔者在这几个月的使用中,几乎没有成功用过这个自动条目创建功能。

而 Chrome 上的自动填充就要比 Kee 好一些,Kee 很多时候即使检测到网页上有可以填充的框,也不会填充进去,而需要用户在框上右键,选择查找匹配的条目才行。Bitwarden 的话,只要处于解锁状态,一般都会检测到密码框并自动填充进去,但如果进入网页时处于锁定状态,则解锁后(此时会立刻匹配网站并显示出对应的密码条目)需要手动点一下对应出来的密码条目,就自动填充了。Bitwarden 的密码匹配的灵活性上和 Kee 基本一致,但在浏览器扩展端的支持更强(而不是所有高级设置都得去 KeePass 桌面程序里调)。可以配置单条目多 URL,也可以配置 URL 上不同的匹配策略,总体上令人满意。

同时,在密码搜索/匹配窗口,事实上匹配(即搜索框留空,与当前 URL 匹配)与搜索(忽略当前 URL,按搜索框内找条目)两种视图下,显示的内容与点击的逻辑是不同的。

  • 匹配视图下,条目的第一个图标是长方型的“查看”,点击可以查看条目详细、修改等。单击条目左侧会直接填充条目。
  • 搜索视图下,条目的第一个图标是向外的箭头,点击会直接跳转到条目的 URL。单击条目左侧进入的是查看视图(等同于匹配视图下点查看键),要填充需要先进入查看视图,在其中选择填充,或保存 URL 后填充。

在新建、编辑条目时,可以使用插件提供的较方便的密码生成功能,点击密码框里的箭头图标即可。在框里已经有密码时,点生成器会警告你是否覆盖旧密码。

在此界面里,点密码后面的箭头会重新生成一个密码,而重叠正方形自然是复制。笔者一般会先在此界面复制好密码,再点上方的“选择”,这样生成的密码会进到上一个画面(即条目创建/编辑画面)的密码框中,填好其它条目,保存即可,接下来在注册界面自动填充,即可把新密码填进去(但修改密码时不可以这样做,如果修改界面要填旧密码,那么建议操作是 进入界面自动填充旧密码->生成新密码并复制->覆盖密码管理器的旧密码,注意一定不要填充->保存条目->在网页新密码框里粘贴新密码并提交)。如果填错了,再粘贴剪贴板里的来修正。

同时,在可以开 TOTP 二因素验证的网站上,也可以粘贴 TOTP 密钥到 Bitwarden 条目中,这样在打开条目详细信息时,可以看到当前的 TOTP 一次性密码。

在 Android 端,笔者认为 Bitwarden 的使用体验好于 Keepass2Android,主要是自动填充成功率高得多,大部分应用和网站都能正确弹出自动填充下拉菜单。

讨论

在笔者搭建、迁移密码管理器方案的过程中,已经注意到了“密码”或者说口令这一悠久的鉴权条件,正在失去其地位。一方面,TOTP、邮箱验证等二因素/多因素辅助鉴权得到发展,而另一方面,国内的短信鉴权、国外的 FIDO2/WebAuthn 等无密码登录方案也开始走入主流,单纯使用密码的鉴权方案必将被废弃。同时,OpenID Connect 等 SSO 方案的发展让整个互联网的鉴权趋向集中化。在大家用的的鉴权服务(Google、Facebook 等)值得信任的前提下,这可以被认为是良好的趋势——越小的公司,越容易出现不良的信息安全实践,而大厂的服务至少技术上是过关的。

同时,笔者也打算从用户的角度写一写对各种鉴权方法的评价。

首先是关于鉴权因素的分类,常见的鉴权主要从三个角度入手:

  • 知道什么(Something you know):密码,或者本地 PIN(后面会详细讲 PIN 和密码的区别);
  • 拥有什么(Something you have):智能卡、手机、短信、邮箱、手环、USB Key 等;
  • 是什么(Something you are):常见生物特征鉴权,如指纹、面部、虹膜等;

上面这是用户端的“鉴权因素”之分。同时,针对不同的验证环境,也开发出来了不同的认证“体系”(注意这些“鉴权体系”和上面的“鉴权因素”很大程度上是正交的):

  • 个人环境,Web / App 环境:各网站直接验证,或使用 OIDC 等进行联合身份鉴权
  • 个人环境,电脑 / 手机开机:以前一般都是本地直接鉴权,现在越来越多用设备/系统提供商给的联合身份鉴权
  • 企业环境,Web 环境:集中鉴权一般用到传统有 SAML、CAS,甚至有 LDAP,现代一点也可能上 OAuth2/OIDC
  • 企业环境,电脑开机与本地服务:Kerberos、LDAP、智能卡、Active Directory 等集中鉴权,最近还有 Windows Hello for Business
  • 拨号上网/VPN 鉴权与记账:RADIUS+CHAP 集中认证

列出来各种已有的手段后,笔者分别进行评价。拨开各种鉴权体系带来的层级,用户最终是要在某个地方使用鉴权因子证明自己的身份的,这里事实上干了两件事:

  • 认证 Identification:用户说“我是谁”
  • 鉴权 Authentication:用户证明其真的是其声明的人

在使用不同的鉴权因素时,上述两阶段的组合关系也可以不同,如用 FIDO2 的时候两者是合在一起做的;但使用密码的时候就需要分开,第一个步骤通过用户提供用户名/邮箱等完成。下方主要集中讨论鉴权这一阶段,除非讨论的手段同时进行认证与鉴权。

同时各种“鉴权因素”并不一定单个使用,而是常常组合使用,先有第一因素,再辅助第二因素。甚至会引入 IP 地址(虽然个人极端反对用 IP 地址作为鉴权因素……说你呢 NFS)、地理位置、设备信息等更辅助性质的因素综合进行鉴权决策。

知道什么

这部分最主要的就是密码,而众所周知密码有多方面的不足:复杂的密码容易遗忘,而简单的密码容易被暴力破解/窥视/碰撞,同时密码本身就容易被冒用,一旦有同密码跨站使用的情况可能被撞库。但这都顶不上密码最大的优点——简单。无论是对用户还是对提供方,使用密码鉴权都是最简单的手段(只有 SMS/E-Mail OTP 能一战)。

当然,还是会有一些实现者、用户错误实现/使用密码鉴权,这里说说笔者遇到过的两种:

  • 不正确使用散列、加盐:有存明文的(哈哈,000webhost)、没加盐的。事实上现在最好的做法是直接用 Argon2 等开源又成熟的密码加盐散列方案;
  • 把散列后的密码传到服务器的:这样就等同于把“密码散列”当成密码了……密码在服务器上必须过一遍明文(但传输过程当然必须加密)才行;
  • 把密码写在标签上:对物理直接攻击零防御;

因此一个比较平衡的方案是使用本文所述的密码管理器,每个站使用不同的、熵够大的密码,再由管理器自动记忆填充。用户只需要记忆一个主密码,并保管好自己机器上的密码库即可。另一个方案是使用 Yubikey 等,用静态长密码模式设置一个超长的密码,按一下按钮自动填写。这样虽然各站用了同一密码,只要不是后台直接存明文,因为密码够长,不太容易被彩虹表爆破。但笔者认为终究是这种静态长密码是不够强的。

另一种基于“知道什么”的鉴权因素是 PIN (Person Identification Number),其定义比较众说纷纭(银行卡密码也是 PIN,但显然和这里定义不同),笔者这里采微软那边看到的定义——即在设备本地用的叫 PIN;可以上云,或设备间迁移的叫密码。PIN 整个校验放在设备本地,且一个设备的 PIN 不能用来校验另一个设备、PIN 也完全不向外传输,因此安全性上可以放松些(如一般 4~6 位纯数字或数字字母组合就可以当 PIN 了)。

从上面的定义,也可以看到 PIN 只能用作设备开机解锁等本地应用,同时 PIN 也可以用来解锁第二把钥匙,作为“拥有什么”的鉴权因素上的一环,如用 PIN 解开一个长的私钥,系统再用这个私钥去登录服务器。读者可以把 SSH 私钥的 Passphrase 与此类比。

拥有什么

这部分鉴权手段一般基于用户持有某个物品,借助这个物品进行鉴权。这个“物品”的两个通常特性:携带信息量较大(如一个 4096 位长的私钥 vs 一个普通的密码)、可以执行较复杂计算(可以进行私钥计算与应答质询),使得“拥有什么”的鉴权方式更加牢固、灵活。缺点则是用户必须好好保管这个“物品”,否则账户的恢复会十分困难,因为并不能轻易允许用户取消/绕过这些基于“拥有什么”的鉴权,否则就失去了其价值。

具体地讲手段,这里开始就百花齐放了,我们尽量把相同使用场景的放在一起说:

  • SSH 公私钥:基于公私钥非对称加密,挺好用的第一鉴权因素,但实现不了认证(用户名还是要提供);
  • FIDO2/WebAuthn:笔者比较喜欢的设备鉴权方案,设备上有不离开设备的私钥,注册时向网站提交某种“公钥”一样的信息,认证/鉴权时则提交这个公钥以及通过私钥签名,响应网站的质询。
    这个“设备”可以由独立硬件充当、在使用时通过 USB 或 NFC 和电脑手机通信,也可以由普通电脑、手机上的安全芯片甚至软件模拟(例如,Windows Hello),但安全性是递减的。
    另外,FIDO2/WebAuthn 合并了认证的功能,因此可以免输用户名,实现真正的无密码登录(当然,把这个作为第二因素也是可以的)。
    用户体感上就是在网站需要登录时,插入一个 USB 设备按一下,或者扫一下电脑的指纹,就登录上了;
  • U2F(Universal 2nd Factor):另一个设备鉴权方案,Google 早期用得不少,现在被称为 FIDO CTAP1,个人感觉和 FIDO2 相较没有太大优势;
  • TOTP(Time-based One Time Password,基于时间的一次性密码,时基一次性密码):RFC 6238 有比较详细的定义,主要原理和银行的电子密码器类似,注册时网站和设备共享一组种子(手机可以扫码,电脑上直接复制粘贴一串字符就行),而后网站和设备都可以在完全不与对方通信的前提下,通过时间计算出一个密码(30s 一换的 6 位密码)。用户在登录时,提交设备上的“当前密码”,服务器端与其通过时间计算出的密码比对即可。其算法设计使得不可能通过一个/一组一次性密码计算出另一个一次性密码,或者通过一组一次性密码反推回种子,保证了一定的安全性。TOTP常常被用作第二鉴权因素;
    在没有 FIDO2/WebAuthn 的情况下,笔者认为 TOTP 也不错。
  • 各种“令牌”/“设备锁”:指 GitHub Mobile、战网安全令这些,通过通信手机上的软件进行鉴权,用户在手机上点一下通过,本质上是利用了手机上的登录对话,而手机上登录的安全性通过其它方法保障(如 QQ 设备锁用手机号,战网安全令甚至用身份证信息)。个人并不喜欢,因为这样一方面依赖一个超持久的登录会话,另一方面不方便迁移。
  • 各种 USB Key:本质上就是私有协议的 FIDO2/WebAuthn,因为是私有协议所以笔者不喜欢。
  • 智能卡:这里特指 Windows 上的智能卡,即那些实现了 Windows Smart Card Minidriver 的设备。一般由实现了 ISO/IEC 7816-4 的智能卡/读卡器充当,但 Yubikey 也实现了这个驱动,甚至还有软件模拟方案。这套在企业里用得较多,因为比较封闭了解不多,不作评价,但笔者对封闭方案也没太好印象;

总的说来,笔者在 Web/App 的环境下喜欢的鉴权手段是 FIDO2/WebAuthn 与 TOTP,且最好能用 FIDO2 做无密码鉴权。然而,FIDO2/WebAuthn 有各种模式,不是所有网站都支持所有模式,如很多网站是不能用“platform”模式的 WebAuthn 的,这样就仅支持使用 Yubikey 等专门的硬件进行登录,而不能用手机指纹、Windows Hello 面容验证了。另一方面 TOTP 只能做第二因素,但其十分简单,甚至可以内置在 Bitwarden 里。可惜国内实现 FIDO2/WebAuthn 的应用并不多,更多用的是下面说的变种。

还有几个“变种”(并不知道把这两个放在这是否恰当):

  • 邮箱一次性密码认证:登录时填写邮箱,网站向邮箱发送包括一次性密码(比如 6 位数字)或链接,用户输入密码或链接,登录系统;
    本质上是把鉴权推到了邮箱侧,有种联合鉴权的味道。缺点是邮箱变成了容易被攻破的点。
    考虑到邮箱也常常被用来作为修改密码的(经常是唯一的)鉴权标准,用它来登录也正常。
  • 手机短信/电话语音一次性密码认证:和邮箱一样,区别是用短信或电话语音发放一次性密码或链接。
    优点是在国内,手机不容易被攻破(SIM Swap 在国内不太可能,因为需要本人去营业厅,本质上是一种“是什么”鉴权),国外 SIM Swap 的风险大一些;缺点是手机号本身具有隐私属性,一个人并不可能有太多手机号,且短信网关是要收费的。

这两个变种一般都拥有较强的鉴权力,且常常被用作单因素鉴权(国内特别多的手机号登录,甚至密码都省了)。同时也多见登录环境异常时,用这两种因素来进行辅助验证,或第一因素不可用(密码遗忘时)的救援措施。

邮箱暂且不说,但笔者事实上对短信一次性密码认证是深恶痛绝的:

  • 手机号可能带来隐私泄露,特别是中国的信息安全现状;
  • 一个人不太会有太多手机号,让多开变得困难,但笔者认为一人多号本就是天经地义的事(银行支付等敏感业务除外);
  • 作为开发者,需要给短信网关上供(不小的一笔钱);
  • 本身,把跨多站的、如此强力的鉴权力绑定在一个(非自己控制)的东西上就是一件令人毛骨悚然的事。一方面手机号的控制权在运营商手里,而现在断卡断得脑袋都要断了,但笔者不敢太妄议朝政,评价是懂的都懂。另一方面如果丢失/换号,难以追踪到底用手机注册了多少服务,用户迁移成本巨大;
  • 手机号很多时候是网络实名制的一环,而笔者对国内现在推行的网络实名制是至始至终旗帜鲜明地强烈反对的;

是什么

这里主要是生物特征手段,即“一个人本身具有的区别于其它人的、最好终生不变的特征”。常用的有指纹、面容、掌纹、虹膜等。成熟的方案也有很多,这里不赘述。Windows Hello 和苹果的 Touch ID、Face ID 均划进此列。

笔者比较不能忍的是很多国内厂商丧心病狂地滥用生物特征手段,特别是人脸,进行鉴权,甚至在公共区域进行这样的操作(如微信/支付宝刷脸支付)。这样既是安全的漏洞,也是隐私泄露的危机。笔者认为生物特征鉴权的一个红线是,对普通应用而言(即除公安/海关/边检/银行这样的特殊业务外,另外公安是否应该对无案底的人留存高精度的特征数据都值得商榷),任何生物特征数据不应进云端。手机上进行指纹面容识别,一般没有这个问题(指纹的话甚至不会进手机 CPU,会在独立的安全芯片上完全处理),但大街上的自动贩卖机显然是会把你的脸传到云上的。

鉴权体系

锐评完了各个鉴权因素,再来看看各个鉴权体系:

  • 各点直接鉴权,对于本地设备,以及仅用密码的网站还好,但对于 Web 端、多站协作、多因素鉴权的情况下,不太经济;
  • Web 场景,内部联合鉴权:联合身份鉴权环境下,一般有一个统一的“身份提供者”(IdP,Identity Provider)提供用户信息的存储与用户鉴权服务。“内部”即接入此服务的各网站/应用被同一个实体控制,如一个学校的 IdP 与学校的各种网络服务(“统一身份认证”)。
    这里传统上就已经有大量方案存在,如 SAML、CAS 等,还有 LDAP。个人并不喜欢这三种,因为均有较高的复杂度,特别是 SAML 和 LDAP 简直是惊天动地,LDAP 完全是古神级别的。新潮的更多是是基于 OAuth2 搓出的轮子,如 OIDC(OpenID Connect),开发体验一般要好些。用户体验不应该有太大的差别,但实际情况下体验会因为实现质量而有参差,如南大统一身份验证采用的金智教育 IDS6 IdP 的 OAuth2 实现就十分糟糕(虽然 LDAP 也没见得有多好)。
  • Web 场景,第三方联合鉴权:和上面的区别是这里的 IdP 一般是较独立的实体,单纯提供一个鉴权业务。常见的 “使用 QQ 登录”、“使用 Google 登录”即属此列,此时一般均使用 OAuth2 搓的轮子。本质上是由这些知名的 IdP 提供快速、安全的登录服务(特别地,很多用户的设备上会有长期的 Google/Facebook 会话,因此可以做到一键跳转登录),一般还不错。
  • 企业环境,电脑开机与本地服务:Kerberos、LDAP、智能卡、Active Directory 等集中鉴权。这些协议都有惊天地泣鬼神的复杂度。138 页的 Kerberos RFC 4120 我楞是看不懂,更别说 LDAP 了(看到 ITU-T 搞的那堆 X 系列的玩意比面对十亿个杀人狂魔更恐怖……,更可怕吔!!而 LDAP 就是基于 X.500 DAP 出来的)。但一方面 企业级™ 场景的应用早就对这些东西形成路径依赖了,另一方面除了复杂这些协议也没太大的硬伤,问题也不大。
  • 拨号上网/VPN 鉴权与记账:RADIUS+EAP+PAP/CHAP 集中认证:一坨臭不可闻的勾式,完全是拨号公司临时想出来的怪物在现代的历史残余……很难想象他们是怎么把这么一个简单的需求做得如此复杂僵硬的,幸好 FreeRADIUS 还算能用。

写在后面

一篇讲 Vaultwarden 的文章,花了近一半的篇幅在评述各种鉴权因素与方案,倒也合理。由于我的评述带有较强的主观色彩,且比较站在上帝视角(如站在 2023 年看 1990s 发明的协议),大家看个乐就好。但作为用户(偶然当实现者),还是希望这些评述能反映一些痛点,让更多实现者能支持更合理的鉴权方案、更多用户能选择适合的鉴权因子。


不想被自己的惰性打败。