ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

AD域账号多电脑同时登录限制:脚本+标记文件方案

AD域账号多电脑同时登录限制:脚本+标记文件方案 简介面向AD域管理员与IT运维人员文档完整讲解如何通过GPMC创建GPO并搭配logon.vbs/logoff.vbs脚本限制Windows域用户多点并发登录解决账号盗用、异常登录等域安全隐患。文档先介绍GPMC组策略管理控制台的基本用法再逐步说明创建并链接GPO的流程然后给出登录脚本与注销脚本的完整代码和配置要点。其中登录脚本会记录用户名称、计算机名称和登录时间并在检测到同一账号已在其他计算机登录时弹出警告并强制关闭旧会话注销脚本则负责记录退出信息方便审计登录轨迹。文档还对共享文件夹写入权限、日志目录路径等关键设置做了说明帮助管理员根据实际域环境调整参数包体为单个docx文档大小404KB结构紧凑可直接查阅后实施。目前已有1724人学习下载适用于需要加强域内账号安全、落实最小权限与合规审计要求的中小型企业网络环境。1. 一个域账号多处登录为什么你的 AD 防不住公司有台文件服务器一个域账号被三个部门共用审计整改通知要求限制“同一个 AD 域账号不能多点并发登录”。接手后我先查了域控发现 Windows 默认根本没有“同一域用户只能在一台电脑在线”的底置限制Kerberos 不拦组策略里也只有针对 RDP 连接数的设置。后来下载到一份《AD域-限制windows域用户多点并发登录.docx》里面用“登录脚本 标记文件”实现了域用户并发限制不用额外买软件在 Netlogon 共享上放一个 PowerShell 脚本、挂一条 GPO 就能跑。这套方案适合本地 AD 域环境下的 IT 运维和安全管理员既能防账号共用又能留下登录审计记录。下面把原理、脚本、部署步骤和踩坑点全部拆开讲。2. 并发登录的机制为什么 AD 域默认管不住以及三条可选路径2.1 Kerberos 眼里没有“会话互斥”很多运维第一次碰到这个需求第一反应是去域控里找开关。找了一圈发现AD 域控制器里根本没有“同一账号最多只能一台机器登录”的全局选项。要理解为什么得从 Windows 的认证过程说起。当一个域用户在一台 Windows 电脑上输入用户名和密码时本机不会直接验证密码而是把凭证发给域的域控制器DC。DC 上运行着 Kerberos KDC 服务会先做 AS-REQ/AS-REP 交换给用户发一张 TGT票据许可票据然后本机再拿着 TGT 去申请 TGS 票据用于访问各种资源。这个过程里DC 只负责验证“你是谁”它并不去查“你是不是同时在别的机器上已经登录”。所以同一账号在电脑 A 拿到 TGT再去电脑 B又一次从 DC 拿到一张权限完全相同的 TGT两边都能正常建立会话。再看 Windows 本地的会话机制。在每台电脑上Winlogon 进程管着当前的交互式登录会话每个会话有 Session ID、用户 Token 等。这些信息只存在于本机域控上面没有一张“全局会话表”。域控最多知道“某账号最近在哪个 DC 上认证过”但那只是认证事件不是在线状态。换句话说AD 的设计初衷就是信任用户默认允许从任何地方同时登录而不是像某些应用系统那样强制单点登录。这个机制的副作用是Linux 里的w、who等命令可以方便地看到“用户当前在线会话”而 Windows 域环境里想看“某个账号当前在哪些电脑上有会话”非常麻烦常见做法是通过qwinsta /server:电脑名逐台去查或者用第三方资产探查工具去扫描内网效率很低。所以想靠 Windows 原生命令直接做限制路走不通。2.2 远程桌面限制挡不住本地登录很多人会把 RDP“限制连接数”当成并发登录限制来用这是个典型的认知误区。在组策略管理模板里确实有“远程桌面服务→远程桌面会话主机→连接→限制连接数量”的设置但它的作用对象是远程桌面会话RDP 会话也就是用户通过 mstsc 远程连到服务器或电脑上的那些会话。用户坐在电脑前面做本地登录属于控制台会话会话类型是 Console根本不走 RDP 这条路所以这个策略对本地登录完全无效。终端服务器上还有一个“限制用户只能使用单个会话”的设置它看上去更接近目标但它需要至少一台 Windows Server 作为会话主机而且只对连接这台会话主机的远程会话生效。如果用户是登录普通工作站的本地桌面这个策略也管不到。真正想做到“所有交互式登录都被约束”必须在登录这个动作本身发生后立刻检查全局状态再做决定。2.3 三条路径的对比与选型目前我接触过的主流做法有下面三种。第一种是组策略限制 RDP 连接数成本最低但覆盖范围只限远程桌面第二种是登录脚本 标记文件覆盖所有交互式登录成本为零维护难度中等第三种是购买第三方身份治理产品比如 NetWrix、ManageEngine 的 AD 管理套件它们能监控登录事件并做告警但多数产品本身并不强制互斥还是要配合脚本或 agent 去注销重复会话而且价格按域用户数量收取对中小网络不算友好。方案覆盖范围部署成本维护成本强制力RDP 连接数限制仅 RDP 会话低低中可绕过登录脚本 标记文件所有交互式登录低中高由 OS 执行第三方 AD 治理工具监控与告警高高中需配合脚本这里推荐登录脚本方案并不是因为它完美而是它在预算和效果之间取一个平衡。脚本不需要修改 AD 架构不需要在每台电脑装 agent只要域内所有成员的登录都由域控认证就一定会在登录时执行到用户配置里的登录脚本。换句话说它是跟着认证过程走的用户在哪儿登录脚本就会在哪儿跑覆盖面天然足够。后面两章就按这个方案从脚本编写、共享目录搭建到 GPO 部署一步步还原。注意这个方案和“密码共享”不同它不是阻止别人知道密码而是确保同一个账号在同一时间只能在一台设备上进入工作环境。3. 标记文件方案从原理到完整落地3.1 标记文件的读写模型标记文件方案的核心是在一个所有域电脑都能访问的网络共享路径下为每个用户维护一个文本文件。这个文件的名字就是用户名内容是“机器名|登录时间戳”。它的读写模型很简单登录时写/检查注销时删除。为什么选网络共享而不是本机某个文件夹因为本机文件只有在本机可见无法知道其他电脑上的会话状态。只有把标记文件放到一个所有电脑都能访问的公共位置这台电脑上的登录脚本才能看到那台电脑留下的标记。通常这个共享放在域控上或者放在一台长期开机的文件服务器上。我的建议是放在单独的共享目录不要和用户文档混在一起避免用户误删或权限错乱。标记文件的命名和内容最好固定方便排查。命名直接用%username%.txt内容用“|”分隔第一段是机器名第二段是时间。机器名不要用 NetBIOS 名和 DNS 名混着用脚本里统一从COMPUTERNAME环境变量取多网卡环境也不会因为 IP 变化而误判。3.2 登录脚本 login.ps1完整版下面的脚本比初版多加了过期标记处理和异常捕获适合直接上线# login.ps1 - 域用户并发登录限制 $markRoot \\dc01\internal\sessions $auditRoot \\dc01\internal\login-audit $sessionTtl 4 # 标记有效期单位小时 $userName $env:USERNAME $machine $env:COMPUTERNAME $markFile $markRoot\$userName.txt $now Get-Date # 白名单退出 $whiteGroup DOMAIN\NoLimitUsers $identity [Security.Principal.WindowsIdentity]::GetCurrent() $principal New-Object Security.Principal.WindowsPrincipal($identity) if ($principal.IsInRole($whiteGroup)) { exit } # 写审计日志 $auditRecord $(Get-Date -Format yyyy-MM-dd HH:mm:ss)|$machine|$userName|LOGIN Add-Content -Path $auditRoot\$userName.csv -Value $auditRecord -Encoding UTF8 try { if (Test-Path $markFile) { $raw Get-Content $markFile -Raw $parts $raw.Trim().Split(|) $oldMachine $parts[0] $oldTimeStr if ($parts.Count -gt 1) { $parts[1] } else { 2000-01-01 00:00:00 } # 如果标记文件超过有效期视为残留直接覆盖 $oldTime [datetime]::Parse($oldTimeStr) if (($now - $oldTime).TotalHours -gt $sessionTtl) { $machine|$(Get-Date -Format yyyy-MM-dd HH:mm:ss) | Set-Content $markFile -Force exit } # 标记有效且来自其他机器 阻止登录 if ($oldMachine -ne $machine) { Write-EventLog -LogName Application -Source LoginGuard -EventID 1001 -EntryType Warning -Message account $userName already logged on at $oldMachine -ErrorAction SilentlyContinue Start-Sleep -Seconds 5 logoff.exe } else { $machine|$(Get-Date -Format yyyy-MM-dd HH:mm:ss) | Set-Content $markFile -Force } } else { $machine|$(Get-Date -Format yyyy-MM-dd HH:mm:ss) | Set-Content $markFile -Force } } catch { # 捕获异常避免脚本报错打断登录 Write-EventLog -LogName Application -Source LoginGuard -EventID 2001 -EntryType Error -Message $($_.Exception.Message) -ErrorAction SilentlyContinue }逻辑说明读取标记后先解析时间如果文件时间比当前时间早超过 4 小时说明这条标记很可能是残留比如上次注销脚本没执行此时直接覆盖为新标记不阻止登录如果时间新鲜且机器名不同就走logoff.exe强制注销。白名单组判断放在最前面属于 NoLimitUsers 组的用户直接退出不参与限制。参数说明$sessionTtl是残留判定阈值实际设置看公司作息如果公司有夜班建议改成 8 小时避免用户半夜换电脑被误伤$auditRoot用来写审计 CSV与标记文件分开目录Write-EventLog的-ErrorAction SilentlyContinue保证了即使事件源不存在也不会中断脚本。要注意logoff.exe是注销当前交互式会话的命令它会把正在运行的程序全部关闭。如果你希望只弹警告而强制断开可以换成msg *给当前会话发提示再配合一个定时器但那种方式用户总能绕过我一般只在测试环境用。3.3 注销脚本 logout.ps1 与共享目录准备注销脚本只用做一件事删除自己的标记文件。写成单行逻辑就行# logout.ps1 - 注销时清理标记 $markRoot \\dc01\internal\sessions $markFile $markRoot\$env:USERNAME.txt if (Test-Path $markFile) { Remove-Item $markFile -Force }这个脚本通过 GPO 的“注销”配置触发但强制断电时它无法执行这是标记文件方案最大的短板。我在生产环境里处理这个短板的手段是把残留清理做在登录脚本里上面的 TTL 逻辑而不是试图保证注销脚本 100% 执行。共享目录的搭建流程分几步在 DC 或文件服务器上创建目录C:\InternalSessions和C:\InternalAudit。分别共享为InternalSessions和InternalAudit共享权限和 NTFS 权限都加上Domain Users的修改权限。把login.ps1、logout.ps1放到域控的 SYSVOL 脚本目录或直接放在\\dc01\Netlogon里注意 Netlogon 默认只读权限需要额外写权限。在客户端用net use手动测试能否访问共享并写入文件。实际操作中我见过很多环境把共享建在域控的 C 盘但有经验的运维会把共享放在专门的文件服务器上因为域控的磁盘 IO 和故障恢复都比不上专用服务器。万一标记文件损坏也不影响域控核心功能。3.4 GPO 关联与客户端验证把 GPO 挂在用户 OU 上路径是“用户配置→Windows 设置→脚本(登录/注销)→登录”。这里有个容易踩的细节脚本类型是 PowerShell 后马上会弹出一个“脚本参数”输入框里面不要填任何内容否则会被当成参数传给 PowerShell导致脚本无法运行。注销脚本同样配置。配置完成后用受限账号登录一台测试机然后到第二台机器用同一账号登录。预期结果是第一台标记文件正常生成第二台登录时出现 5 秒延迟后注销。如果第一台机器上正好有打开的文档会被强制注销关掉测试前记得保存。这时可以去共享目录里看标记文件是否被更新。如果第二台机器注销成功且注销脚本执行了标记文件会被删除这会导致第一台机器再登录时重新创建标记不会出问题。4. 避坑手册为什么你的限制方案老翻车4.1 登录脚本没运行账号照样双开现象所有脚本文件都放好了但用户仍然能在两台电脑上同时登录没看到任何注销动作。原因最常见的是 GPO 没有应用到用户所在的 OU。脚本绑定到用户配置后用户登录时必须能读到 GPO如果用户的计算机和用户对象都在不同 OU或者组策略被 Block Inheritance 挡住脚本就不会执行。其次是写脚本时用了本地文件路径比如C:\scripts\login.ps1但组策略要求脚本在域控的 SYSVOL 共享上才能被所有客户端访问。解决用gpresult /r username查看用户实际应用了哪几条 GPO确认 LoginGuard 策略在内。登录脚本路径一定要是 UNC\\域名\SYSVOL\域名\Policies\...或\\dc01\...不要用本地盘符。执行策略导致脚本没跑的情况也会出现先把 GPO 里 PowerShell 执行策略设为 Bypass再在命令行用powershell -ExecutionPolicy Bypass -File \\dc01\...\login.ps1手动跑一遍。4.2 强制断电后标记残留第二台电脑被误杀现象一个用户今天在 A 电脑登录后正常下班但没有正常注销而是直接断电第二天他在 B 电脑登录时被强制注销A 电脑明明已经没人在用了。原因断电时logout.ps1根本来不及执行标记文件里还写着 A 电脑的机器名登录脚本认为“另一台机器在线”于是注销 B。解决给标记文件加时间戳并做过期判断。在login.ps1中读取时间时如果发现标记文件的修改时间已经超过 N 小时比如 4 小时就视为失效标记直接覆盖并用当前机器重新写标记。这样至少能扛过隔夜或午休掉电的情况。更进一步的做法是将检测逻辑改成“标记文件 对方机器还在线”双重校验但查远端机器在线性需要额外网络探测不建议在登录脚本里做重活。4.3 写标记文件报“拒绝访问”现象登录脚本带红色报错事件日志显示UnauthorizedAccessException标记文件没有生成。原因两个权限点没配对。共享权限给了 Domain Users但 NTFS 权限没继承或者域控上启用了 SYSVOL 的共享权限覆盖。解决右键共享文件夹把共享权限和 NTFS 权限同时加上“Domain Users 修改”。如果脚本还要写事件日志记得源“LoginGuard”不存在会报错先在任意一台机器上执行New-EventLog -LogName Application -Source LoginGuard创建事件源否则Write-EventLog那行会把整个脚本带崩。4.4 注销脚本删除不了共享标记现象正常注销后标记文件仍然躺在共享目录里下次登录被误杀。原因logout.ps1执行时机太晚网络连接可能已经挂掉或者权限不足。尤其是注销时如果弹出了“关闭程序”对话框脚本会等用户点击才继续此时网络已经断开。解决把logout.ps1里的删除逻辑改成“延迟重试”如果在Test-Path阶段发现无法访问则等待 5 秒后再试一次。还可以把标记文件的删除放在共享端的计划任务即客户端不直接删文件而是写入一条“离线”记录由服务器侧的定时任务统一清理。小规模环境接受“标记偶尔残留”靠时间戳容错撑着即可。4.5 同账号在同一台电脑上反复登录被频繁清理现象一台电脑上用户来回注销再登录偶尔会触发“已在其他机器登录”的提示并被注销。原因快照类虚拟机或克隆机器使用相同计算机名导致标记文件里的机器名和当前机器名一致本不会误杀但如果集群环境出现多个同名计算机名或者用户在登录界面切换账户时旧标记未清理就可能触发残留误判。解决在脚本里加一个“机器名标记时间”的二次确认。如果标记文件里的机器名相同但时间戳距离当前时间极近比如 30 秒内有可能是快速切换用户不是新鲜会话不必注销。实际部署时我会先把所有测试机的计算机名改成唯一值再观察标记文件内容判断是否正常。5. 进阶用法按安全组放行、审计日志和终端服务器补充限制5.1 只限制特定 OU 或安全组全员限制往往会引起反弹尤其是一些经常需要出差、在多地同时登录的高管。更常见的管理需求是只对普通员工限制对 IT 运维、高管放行。这个需求可以在登录脚本里加一个“放行名单”或“限制组”判断来实现。放行名单很简单在login.ps1里加一段$whiteListGroup DOMAIN\NoLimitUsers $identity [Security.Principal.WindowsIdentity]::GetCurrent() $principal New-Object Security.Principal.WindowsPrincipal($identity) if ($principal.IsInRole($whiteListGroup)) { exit }这段代码把当前用户的组信息取出来如果他在“NoLimitUsers”组里就直接退出登录脚本不做检查、不写标记、不受限制。相应地需要限制的人只要不在这个组就行。这样改造后GPO 不用拆成多条只需维护一个 AD 安全组。另一种写法是用 OU 判断从$env:USERDNSDOMAIN或 LDAP 查询用户所属 OU按 OU 放行。但 AD 里面一个用户可能属于多个组OU 只有一个所以按组维护的落地成本比 OU 低。我在实际项目里一般先把所有域账号放到“普通用户”组然后单独建“不受限用户”组在脚本里做白名单。5.2 登录行为写入 CSV 做审计很多企业不是为了防双开而是想拿到“谁、在什么机器、什么时间登录”的完整记录以应对审计。标记文件方案顺手就能把审计数据留下来在login.ps1里增加一段$logFile \\dc01\internal\login-audit\$env:USERNAME.csv $time Get-Date -Format yyyy-MM-dd HH:mm:ss $record $time,$machine,$env:USERDNSDOMAIN Add-Content -Path $logFile -Value $record -Encoding UTF8这段代码每次登录都追加一行字段包含时间、机器名、域。如果同一个账号短时间内出现两次登录记录且机器名不同就等于是并发脚本的一次实锤。把它接到 Splunk 或其他日志平台还能做实时告警。要注意日志文件所在的共享和标记文件分开管理上更干净也避免日志写入影响标记判断。共享权限仍然给 Domain Users 写权限但审计文件所在的文件夹建议再建一个子目录并限制访问。5.3 对特定应用或终端服务器的补充限制标记文件方案管的是“交互式登录”如果用户通过 RemoteApp 或 Web 访问终端服务器登录脚本不一定会在目标机器上触发。这时需要叠一道 RDP 策略限制同一账号的远程桌面会话数比如在终端服务器上配置“限制用户只能使用单个会话”。组策略的路径是“计算机配置→管理模板→Windows 组件→远程桌面服务→远程桌面会话主机→连接→限制连接数量”把这个数量设为 1再配合脚本标记基本能覆盖本地登录 RDP 并发两条路径。注意这个策略的生效对象是终端服务器不是客户端。5.4 定时清理残留标记与健康检查标记文件方案最怕的就是残留除了脚本里的 TTL 兜底我还会在共享服务器上挂一个每日计划任务凌晨把所有超过 12 小时的标记文件清一遍。清理脚本用一个定时任务执行# Cleanup-StaleMarks.ps1 $markRoot \\dc01\internal\sessions Get-ChildItem $markRoot -Filter *.txt | Where-Object { $_.LastWriteTime -lt (Get-Date).AddHours(-12) } | Remove-Item -Force这个脚本不关心文件内容只看修改时间避免人工去逐个判断。时间阈值要和登录脚本里的$sessionTtl协调如果登录脚本的 TTL 是 4 小时这个清理任务可以放在 12 小时作为最底层保险。清理任务运行在服务器上用的权限是服务器本地管理员不受客户端网络断开影响。6. 验证这套方案是否真的生效会话检查与双登录压测6.1 用 query user 确认会话状态当用户被注销之后你需要在域控或客户端上确认该账号的会话是否存在残留。query user是 Windows 自带的命令输入query user 用户名可以列出该用户在当前机器上的所有会话。如果脚本正常工作新登录的机器上该会话会立即消失旧的机器上只剩下原来那一条。还可以用qwinsta查看服务器上的会话列表更适合终端服务器场景。6.2 一次标准双登录测试测试前先准备两台测试电脑都加入域用同一受限账号在 A 机器登录确认标记文件生成并写入了机器名。然后到 B 机器用同一账号登录观察是否有 5 秒等待和注销动作。这里要注意如果 A 机器在登录后没做任何操作而直接锁屏B 机器上登录时脚本读到标记文件里还是 A 的机器名会正常注销 B。测试完成后回 DC 的共享目录里看当前标记文件是否被更新回 A。如果 B 注销脚本执行时把标记删了那就说明注销脚本正常如果没删标记文件保留在 A后续 B 登录会报“已在 A 处登录”就需要排查 logout 脚本。6.3 事件日志误报排查我在实际部署时很依赖 Application 事件日志里的“LoginGuard”事件。脚本每次阻止登录会写一条 1001 事件里面记录了旧机器名。发现有人被误杀时先拉这条事件看旧机器名是不是当前机器名如果是说明残留导致误判按第 4 章的过期逻辑处理。从那以后我每次给客户做 AD 安全加固都会强制走一遍“双登录 事件日志核对”的流程先在隔离 OU 验证再放量生产。这套脚本很简单但用得好不好取决于你愿不愿意花半小时测出边界。希望帮到你。本文还有配套的精品资源点击获取
返回列表