如何在香港服务器的 Windows Server 2022 上配置多站点 ADFS,确保跨境用户认证的高可用性

周五晚上 22:40,我站在香港机房 19°C 的冷风里,手里那杯便利店买的热美式已经不热了。白天我们收到了来自华南几个分部的抱怨:用户登录公司门户时延飙到 6~8 秒,偶发 502。我们的 ADFS 之前是“单站点 + 单 VIP + 单对 WAP”,部署在香港一个机房里,正常情况下没问题,但一旦跨境网络抖动,就会把认证流程放大出各种毛刺。于是我决定趁着周末窗口,把 ADFS 改造成“香港双机房多站点 + 跨站点 GSLB + 就近接入 + 失效切换”。这篇文章,就是那一晚到第二天清晨我从零到一完成并验证多站点 ADFS 的完整过程、踩到的坑和解决方案。
目标与设计要点
- 跨境(内地↔香港)用户就近接入:降低 TLS 握手与 claims 交换的时延。
- 多站点高可用:任一机房宕机,不影响外网用户登录。
- 统一的联邦服务名(FSN):fs.example.com,无感知切换。
- WAF/防火墙合规与证书链可达(CRL/OCSP):避免因为吊销检查导致的间歇性超时。
- 尽量保持简单:Windows Internal Database(WID)起步,如后续并发上来再平滑切到 SQL Always On。
拓扑总览(实网等效示意)
拓扑:
┌──────────┐
Internet ─▶│ GSLB │──┐ Geo/Latency routing + HC
└──────────┘ │
│
┌────────────▼────────────┐
│ │
┌──────▼──────┐ ┌──────▼──────┐
│ HK-SiteA │ │ HK-SiteB │
│ (MEGA-i) │ │ (CAMPUS) │
└──────┬──────┘ └──────┬──────┘
│ │
┌────────▼────────┐ ┌────────▼────────┐
│ WAP-A(Proxy) │ │ WAP-B(Proxy) │ 外网443
└────────┬────────┘ └────────┬────────┘
│ 443/49443 │ 443/49443
┌────────▼────────┐ ┌────────▼────────┐
│ ADFS-A (Farm) │◀──────▶│ ADFS-B (Farm) │ 内部443
└────────┬────────┘ └────────┬────────┘
│ │
┌─────▼─────┐ ┌─────▼─────┐
│ Domain DC │ │ Domain DC │ 88/389/636/3268...
└───────────┘ └───────────┘
硬件与网络参数(我现场用的这一套)
| 角色 | 机型/规格(示例) | OS/版本 | NIC | 存储/RAID |
|---|---|---|---|---|
| ADFS-A/B | 1U 双路(Xeon Silver/Gold 级别),32~64GB RAM | Windows Server 2022 Std | 2×10GbE | RAID1 SSD 480GB |
| WAP-A/B | 1U 单路,16~32GB RAM | Windows Server 2022 Std | 2×10GbE | RAID1 SSD 240GB |
| DC/GC | 既有(双机房各 2 台,GC 开启) | Windows Server 2019/2022 | 10GbE | RAID1 SSD |
| 负载均衡 | 各站点 F5/HAProxy(L4),对外 WAF 一层 | - | - | - |
| 线路 | 双上联(CMI + CTG/CU),GSLB 走 Anycast/Geo 智能解析 | - | - | - |
网络与防火墙(必开端口汇总):
| 源 → 目标 | 端口 | 说明 |
|---|---|---|
| 客户端 → WAP | 443 | 外网 TLS 入口 |
| WAP → ADFS | 443, 49443 | 443 为 ADFS 服务,49443 为 ADFS Proxy Trust 通道 |
| ADFS → DC/GC/DNS | 88, 389/636, 3268/3269, 53 | Kerberos/LDAP/GC/DNS |
| ADFS → SQL(可选) | 1433 | 如后续迁移到 SQL Farm |
| WAP/ADFS → CRL/OCSP | 80/443 | 证书链与吊销检查(务必保证在内地可达) |
| 管理/监控 | 5985/5986, 22/443 | WinRM/Prometheus-Exporter/Agent 等(按需) |
证书与命名方案(避坑重点)
| 用途 | 证书类型 | 主题/主机名 | 颁发建议 |
|---|---|---|---|
| 联邦服务 SSL | 公网 DV/OV 通配/SAN | fs.example.com |
公有 CA(在内地有加速的,如 DigiCert/GlobalSign) |
| WAP 入口(同上) | 同联邦服务 | fs.example.com |
同上 |
| ADFS 服务通信 | 内网企业 CA 或同上 | fs.example.com |
统一 |
| Token 签名/解密 | 内部证书(非对外) | - | ADFS 自动生成/导入 |
要点:证书链的 CRL/OCSP 访问在内地是否通畅,直接影响 1~2 秒登录抖动。我踩过坑:某公有 CA 的 CRL 域名在部分运营商偶发超时,最后换到在内地有 CDN 的 CA 才平稳。
先决条件与准备动作
- 域功能级别:Windows Server 2016+(我环境为 2019);
- 时间同步:ADFS/WAP 与 DC 时钟偏差 < 5 分钟(最好 < 30 秒);
- DNS:内外网均能解析 fs.example.com(内网指向各站点 ADFS VIP,外网指向 WAP GSLB);
- gMSA:为 ADFS 准备托管服务账号(推荐);
- 站点内负载均衡:L4 直透 443,不做 TLS 解密,保持会话(source IP 或 cookie),健康检查指向 /adfs/ls/idpinitiatedsignon.htm。
实操步骤(完整脚本与命令)
1)创建 KDS 根密钥与 gMSA(在任意一台管理机/域控)
# 创建 KDS 根密钥(生产建议提前10小时生效,这里演示立即生效)
Add-KdsRootKey -EffectiveImmediately
# 创建 gMSA
New-ADServiceAccount -Name gmsaAdfs `
-DNSHostName fs.example.com `
-PrincipalsAllowedToRetrieveManagedPassword "DOMAIN\ADFS-Servers"
# 给 ADFS 服务器加入到 ADFS-Servers 组(预建)
# 并在每台 ADFS 节点上安装并绑定 gMSA
Install-ADServiceAccount gmsaAdfs
Test-ADServiceAccount gmsaAdfs
2)在站点 A 安装 ADFS 并创建 Farm(Windows Server 2022)
Install-WindowsFeature ADFS-Federation -IncludeManagementTools
# 导入 SSL 证书到 LocalMachine\My,记下指纹
$thumb = "<证书指纹去空格>"
# 创建 ADFS Farm(WID 起步)
$cred = Get-Credential "DOMAIN\gmsaAdfs$" # gMSA 选择“服务账户”形式,无密码
Install-AdfsFarm `
-CertificateThumbprint $thumb `
-FederationServiceName "fs.example.com" `
-FederationServiceDisplayName "Example Federation" `
-ServiceAccountCredential $cred `
-OverwriteConfiguration
说明:Windows Server 2022 上的 ADFS 实际“农场行为级别(FBL)”仍为 2019 代系,功能如“Extranet Smart Lockout”等都可用。
3)将站点 B 加入同一 Farm
在站点 B 的 ADFS 节点:
Install-WindowsFeature ADFS-Federation -IncludeManagementTools
$thumb = "<同一张 fs.example.com 证书指纹>"
Add-AdfsFarmNode -CertificateThumbprint $thumb -PrimaryComputerName "adfs-a.domain.local"
WID 复制:Farm 中仅一台为 Primary(可切换),配置通过 WID 自动复制到其他节点。并发量上来可考虑迁移到 SQL AO。
4)启用 IdP 测试页与安全策略
# 便于健康检查与人工测试
Set-AdfsProperties -EnableIdPInitiatedSignonPage $true
# 启用 Extranet Smart Lockout(强烈建议)
Set-AdfsProperties -ExtranetLockoutThreshold 10 `
-ExtranetObservationWindow (New-TimeSpan -Minutes 30) `
-ExtranetLockoutEnforcementEnabled $true
5)部署各站点 WAP(Web Application Proxy)
在每个站点的 WAP 节点:
Install-WindowsFeature Web-Application-Proxy -IncludeManagementTools
# 导入对外 SSL 证书(同 fs.example.com),记录指纹
$thumb = "<证书指纹>"
# 与内部 ADFS 建立 Proxy Trust(注意 443/49443 通)
$cred = Get-Credential "DOMAIN\gmsaAdfs$"
Install-WebApplicationProxy `
-FederationServiceName "fs.example.com" `
-CertificateThumbprint $thumb `
-FederationServiceTrustCredential $cred
防火墙要点:务必放行 WAP→ADFS 的 TCP 49443,否则你会在事件日志里看见 Proxy Trust 定期续约失败(Event ID 276/364),导致偶发 503/502。
6)站点内 LB 与 GSLB 配置(我采用的策略)
站点内 LB(L4 直透):
- ADFS 内部 VIP:adfs-int-a.example.com(A 站),adfs-int-b.example.com(B 站)
- 健康检查:https://<VIP>/adfs/ls/idpinitiatedsignon.htm 返回 200/OK
- 持久性:Source-IP 粘性 15 分钟
GSLB(外网 WAP VIP):
- 记录:fs.example.com → GSLB 智能解析,健康监测各站点 WAP VIP
- 策略:优先按地理/时延分配(内地→站点 A/B 就近),站点失效自动摘除
TTL:30~60 秒(足够快的切换,兼顾 DNS 负载)
7)DNS 内外分离解析
| 记录名 | 内网解析 | 外网解析 |
|---|---|---|
| fs.example.com | 指向各站点 ADFS 内部 VIP(可 GSLB) | 指向 WAP 外部 VIP(GSLB 智能) |
| adfs-a/b.domain | 各自主机 A 记录 | - |
为什么内外要区分? 内网客户端应直接打到内部 ADFS VIP(少一跳),外网才走 WAP。否则内部也绕 DMZ,会徒增时延与失败面。
8)添加(或迁移)Relying Party Trust(以 O365/SAML 应用为例)
# 示例:添加基于元数据的 RPT(SAML)
Add-AdfsRelyingPartyTrust `
-Name "HR-Portal" `
-MetadataUrl "https://hr.example.com/saml/metadata" `
-SigningCertificateRevocationCheck None # 若对端证书链不稳定可临时降低
典型声明规则(示例):将 userPrincipalName 与 mail 同步给对端
c:[Type == "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn"]
=> issue(Type = "upn", Value = c.Value);
c:[Type == "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress"]
=> issue(Type = "email", Value = c.Value);
健康检查与性能验证
我在两地各拉了一个跳板机,跑如下脚本:
# 基础连通
Test-NetConnection fs.example.com -Port 443
# 探测 IdP 页面
(Measure-Command { Invoke-WebRequest https://fs.example.com/adfs/ls/idpinitiatedsignon.htm -UseBasicParsing }).TotalMilliseconds
# ADFS Farm 与 Proxy 状态
Get-AdfsSyncProperties
Get-WebApplicationProxyConfiguration
Get-AdfsProperties | fl ExtranetLockout*,EnableIdPInitiatedSignonPage
上线前后的对比(实测 50 样本平均)
| 地点 | 优化前(单站点)平均首包 RTT | 优化后(多站点+GSLB)平均首包 RTT | 端到端登录(SAML/OIDC)P95 |
|---|---|---|---|
| 深圳 | 210~280 ms | 90~120 ms | 1.3 s → 0.7 s |
| 广州 | 230~320 ms | 100~130 ms | 1.5 s → 0.8 s |
| 上海 | 260~340 ms | 120~150 ms | 1.7 s → 0.9 s |
运营级别的“坑点”与解决手记
CRL/OCSP 可达性
WAP/ADFS 对外证书链每次握手做在线吊销检查,个别公有 CA 的 CRL 在内地随机超时。解法:换到在内地有 CDN 加速的 CA;或在 Proxy 上启用更长的 TLS 会话复用,尽量减少完全握手次数(LB/WAF 配合)。
49443 未放行
你会在 WAP 上见到 Proxy Trust 周期性失败(Event 276),用户随机 503。解法:对等防火墙双向放行 WAP→ADFS 的 49443。
时间漂移
Kerberos/Token 验证对时钟很敏感,漂个 3~5 分钟就会有奇怪报错。解法:所有节点与 DC/NTP 对齐,VMware/Hyper-V 不要双重授时。
WAF 强制 TLS 降级/重写报头
有的 WAF 误判会插手 SNI/Headers,造成 ADFS MSIS7065。解法:对 fs.example.com 放行透明模式,不做 HTTP 修改。
DNS TTL 太长
站点故障时切不下来。解法:GSLB 记录 TTL 控制在 30~60 秒,并对运营商递归器做健康度监测。
WID 的边界
当 RPT/Claims 规则很多、并发大(>10k auth/min),WID 同步/主节点压力会显著上升。解法:提前预案 SQL AO,迁移步骤见下。
IdP 页面默认关闭
2019+/2022 默认关闭 IdP Initiated SignOn,健康检查容易 404。解法:临时开启,仅用作 HC 与故障排查,上线后可只允许专用探测源 IP 访问。
从 WID 平滑迁移到 SQL(预案摘要)
新建 SQL Server Always On(双站点各一节点,监听器跨站点)。
在现有 Farm 上执行:
Set-AdfsProperties -ConfigurationDatabaseConnectionString "Data Source=<SQLListener>;Initial Catalog=AdfsConfiguration;Integrated Security=True"
验证各节点切换到 SQL,确认 RPT/Claims 全量在库。
将 Primary 切至另一站点演练故障切换。
安全与合规模块(我上线时做的最小集)
- 密码喷洒与暴力破解:开启 ESL(上文已给命令),并在 WAF 层做速率限制。
- TLS 配置:仅启用 TLS 1.2/1.3(如环境允许),禁用弱套件;可用 IISCrypto 或 GPO 统一。
- 审计:开启 ADFS 审计日志,与 SIEM(如 Splunk/ELK)对接;关键事件:1200/1202/411。
- 最小暴露面:仅开放 /adfs/ls/、OIDC/OAuth 必需端点;阻断不必要旧协议(WS-Trust 2005/13 如无需可禁)。
- 备份:导出 Token 签名/解密证书与私钥,妥善保管;Farm 配置用 Export-AdfsDeploymentSQLScript 或 Get-AdfsProperties 备档。
变更与回滚计划(我实际执行的顺序)
- 预先布好 ADFS/WAP 与站点内 VIP,保持“黑暗发布”状态。
- GSLB 新建 fs-gray.example.com,让小流量真实用户灰度。
- 观测 24 小时:失败率、RTT、ESL 告警。
- 切主:将 fs.example.com 的流量策略从单站点改为多站点。
- 如异常:一键将某站从 GSLB 摘除(不需回滚服务器配置)。
常用排错清单(现场速查)
| 现象 | 日志/工具 | 可能原因 | 快速动作 |
|---|---|---|---|
| 偶发 502/503 | WAP:Microsoft-Windows-Web Application Proxy/Admin |
49443 被阻断/证书无效 | 放行 49443/检查 Proxy Trust 证书期与链 |
| 登录卡在 3~5 秒 | ADFS Admin/Security | CRL/OCSP 超时 | 换 CA/优化吊销检查可达性 |
| 某地用户全体超时 | GSLB/监控 | 智能解析误判/线路故障 | 临时固定解析到另一站点 |
MSIS7065/MSIS3173 |
ADFS Admin | WAF 改写/协议未启用 | 关闭改写/只启用必要协议 |
Event 276(Proxy Trust) |
WAP Admin | 时间漂移/49443 问题 | 校时/放行端口/重建 Trust |
关键配置清单(摘录)
# 绑定/替换 SSL 证书(ADFS)
Set-AdfsSslCertificate -Thumbprint "<thumb>"
# Token 生命周期(按业务调优)
Set-AdfsProperties -PersistentSsoLifetimeMins 4320 -SsoLifetime 480
# OIDC/OAuth 客户端(示例)
Add-AdfsClient -Name "intranet-app" -ClientId "intra-oidc" -RedirectUri "https://intra.example.com/auth/callback" -ClientType Public
# 导出 ADFS 配置(审计/备份)
Get-AdfsProperties | fl * > C:\adfs-props.txt
Get-AdfsRelyingPartyTrust | Select Name,Identifier > C:\adfs-rpt.txt
成本与容量评估(我用过的一套粗估)
| 项 | 数量 | 单价(示意) | 月成本(示意) | 备注 |
|---|---|---|---|---|
| 物理/云主机(4 台) | 4 | $$ | $$ | 两站点各 ADFS+WAP |
| 负载均衡/WAF | 2 | $$ | $$ | 站点内 LB + 外层 WAF |
| 证书(OV 2/3 年) | 1 | $$ | - | fs.example.com |
| GSLB 服务 | 1 | $$ | $$ | 健康检查+就近/时延策略 |
| 运维监控 | - | - | $$ | 日志/告警/拨测 |
容量方面:以 P95 1 秒内完成登录为目标,ADFS 单节点能轻松顶 2k~3k TPS 的 token 请求(视声明规则复杂度而定)。双站点四节点足够大多数企业规模,后续可横向扩。
灰度、压测与上线(我的节奏)
- 灰度:10% 员工解析到 fs-gray.example.com,对比登录成功率与时延曲线。
- 压测:用内部工具模拟 OIDC/SAML 流程,打到 1.5× 生产峰值,观测 ADFS CPU/Queue Length、WAP ActiveConnections。
- 上线:变更窗口内切换 GSLB 主记录,观察 2 小时。
- 复盘:第二天把 ESL 告警峰值、WAF 速率限制命中、DNS 递归器缓存命中率拉出来开会复盘。
- 尾声:清晨 5:10 的机房门口
切到多站点 GSLB 的那刻,我盯着看板上三条最敏感的时延曲线:深圳、广州、上海。它们从 200 多毫秒一路滑到 100 左右,偶发的红点也消失了。机房门口的灯带还在冷冷地亮着,保安大叔路过冲我点头。我回了个“OK”的手势,把那杯已经凉透的咖啡扔进垃圾桶。
对多数人来说,登录能快 0.5 秒并不值得兴奋;但对我来说,那 0.5 秒背后,是一套经得住跨境网络波动、机房故障、证书链抽风的体系。多站点 ADFS 并不神秘,关键在于尊重“链路可达性、证书可验证、DNS 可控、状态可观测”这四个字。
如果你也正被跨境登录折磨,不妨照着我的这套实践打个样,再按你们的规模和预算做加减。我更愿意把它视作一个“最小正确闭环”,从这里开始,去迭代出真正适合你们业务的联邦认证基座。