允许每个账户一个会话的策略,看起来已经尘埃落定,直到第二次登录到来。可能出现两种结果:新来者替换正在运行的会话,或者旧会话保住槽位而新来者被拒。只有第二种才是首个会话胜出的策略。大多数人会去用的那个客户端选项开启的是并发,因此并不会产生这种结果。

一个设置背后藏着两份契约

关闭并发时,第二个携带相同证书名的连接可以把正在运行的会话顶掉。打开并发则允许同时存在多个会话,这与限制恰恰相反。两种状态都不是拒绝策略,所以决策必须放到别的地方去做。

在动手实现之前先把契约写下来:谁会被拒、用什么证据识别现有会话、以及会话在没有干净断开的情况下死掉时该怎么办。没有人这样表述过的策略,事后无法验证。

驱逐发生在你的连接钩子之前

在 2.6 的服务器代码路径中,关闭并发时,新客户端的早期准备工作会移除同名的既有实例。这次移除发生在连接处理链运行之前。因此,一个发现重复并返回失败的脚本,是在旧会话已经被丢掉之后才拒绝新来者,这恰恰是策略想要避免的结果。上游实现中的回调顺序,才是你自己构建唯一可靠的答案。

StageWho can still refuseEffect on the incumbent
Certificate checkServer trust configurationNone
Authentication decisionThe authentication controllerNone, because establishment has not run
Connect handlerA profile scriptAlready evicted in the same-name case
Post-establishmentStatus tooling, monitoringBoth sessions already exist or one is gone

轮询状态文件同样不是闸门。两个同时到达的尝试都可能在任意一方提交之前看到空槽位,于是两者都继续。

把决策移进认证环节

证书校验之后,管理接口可以接管认证决策。在这种安排下,控制器不是监视器:它接收每一个正在连接的客户端实例,并回答该实例是否放行。由于答复在建立之前到达,拒绝不会动到现有会话。同一条通道还会上报断开,控制器据此知道某个槽位是被合法释放的。

每一个新实例都必须过这道闸,包括重连以及任何不经提示就重新认证的路径。有些实例永远到不了的闸门保护不了任何东西,而这种遗漏会一直隐形,直到有人去试那条旁路。

令牌可以绕过这道闸

一旦令牌签发,服务器就可以在内部完成校验,并为该连接跳过外部认证。管理调用根本不会发生,所以只在那里实现的槽位检查永远看不到新实例。这种做法对用户是便利,对并发策略却是窟窿。让令牌认证的连接也走一遍等效的权威检查,或者在策略要求每个连接都做决策时关掉令牌路径。

不要用无条件接受所有令牌来弥补。过期或无效的令牌仍然必须失败,而且必须和其他一切走同一个决策点失败。

把预留绑定到实例,而不是地址

地址是很弱的身份:同一台笔记本的重连可能来自另一个地址,而被复制的凭据也可能来自同一个地址。把预留绑定到服务器分配的客户端实例上,这样对已放行实例的重新认证就会被认作所有者。新的实例标识必须通过先到先得拿到自己的槽位,即使它出示的是同一个令牌。

只释放失败尝试所属的那份预留。旧尝试延迟到达的断开事件不得清掉新所有者的槽位,这就是释放要指名实例而不是账户的原因。控制器重启之后,先对活跃会话做一次核对再放人进来,并且对接入该账户的每个网关都适用同一规则。

真正过期的槽位如何回收要另行决定。管理员需要一条有文档记录的清理途径,而这条途径不应该让只是点了重连的用户够得着。

测试真正重要的组合:两次几乎同时的登录、干净断开后的重连、现有会话的重新认证、被拒的新来者,以及控制器重启。每一种情形下,现有会话都必须保持可用,新来者必须被拒,且不牵连其他账户。

相关文章