Get-AzureADAuditSignInLogs调用触发节流限制的规避方案
Azure AD 登录日志拉取节流问题修复方案
核心原因
当前实现触发429节流有三个明确问题:
- 使用
-All:$True一次性拉取7天全量日志,单请求承载数据量过大,直接触发接口单请求负载阈值 - 重试逻辑使用固定200秒休眠,没有读取接口返回的
Retry-After响应头要求的等待时长,要么等待时长不足触发二次节流,要么等待过久浪费时间 - 所用2.0.2.105版本的AzureADPreview模块本身存在节流处理缺陷,内置自动分页的请求密度过高,不会自动适配节流规则
非Graph API的可行解决方法
- 拆分查询时间窗口:将7天的查询范围拆分为1-2小时粒度的小时间块逐块拉取,将单请求返回数据量控制在1万条以内,从根源降低单请求负载,这是最有效的规避手段
- 动态重试逻辑:捕获429异常时从响应头读取
Retry-After值作为休眠时长,设置最多5次的重试上限,解决固定时长重试的适配问题 - 手动控制分页节奏:弃用
-All:$True的自动快速分页逻辑,手动指定-Top 1000(接口支持的单页最大返回条数),每拉取完一页休眠2-3秒再请求下一页,控制单位时间内的请求频率 - 升级模块版本:将AzureADPreview模块升级到2.0.2.177以上的稳定版本,修复旧版本内置的节流适配bug
注意:不要使用多线程、多账号并发的方式尝试绕过节流,AAD审计接口的节流计数是按租户维度统计的,并发请求只会触发更长时间的限流。
修正后可直接运行的代码
# 拉取最近7天的Azure AD登录日志 CLS $totalDays = 7 $windowHours = 2 # 每2小时一个查询块,日志量大可调整为1小时 $maxRetries = 5 $Report = [System.Collections.Generic.List[Object]]::new() $currentEnd = Get-Date $currentStart = $currentEnd.AddDays(-$totalDays) Write-Host "开始拉取Azure Active Directory登录日志..." while ($currentStart -lt $currentEnd) { $windowEnd = $currentStart.AddHours($windowHours) if ($windowEnd -gt $currentEnd) { $windowEnd = $currentEnd } # 格式化时间为过滤器要求的UTC格式 $filterStart = Get-Date $currentStart -Format "yyyy-MM-ddTHH:mm:ssZ" $filterEnd = Get-Date $windowEnd -Format "yyyy-MM-ddTHH:mm:ssZ" $filter = "createdDateTime ge $filterStart and createdDateTime lt $filterEnd" $retryCount = 0 $windowSuccess = $false while (-not $windowSuccess -and $retryCount -lt $maxRetries) { try { Write-Host "正在拉取时间窗口 $filterStart 至 $filterEnd 的日志..." # 手动分页,单页1000条,弃用-All参数 $page = Get-AzureADAuditSignInLogs -Filter $filter -Top 1000 # 处理当前页数据 foreach ($Rec in $page) { switch ($Rec.Status.ErrorCode) { "0" { $Status = "Success" } default { $Status = $Rec.Status.FailureReason } } $ReportLine = [PSCustomObject] @{ TimeStamp = Get-Date($Rec.CreatedDateTime) -Format g User = $Rec.UserPrincipalName Name = $Rec.UserDisplayName IPAddress = $Rec.IpAddress ClientApp = $Rec.ClientAppUsed Device = $Rec.DeviceDetail.OperatingSystem Location = $Rec.Location.City + ", " + $Rec.Location.State + ", " + $Rec.Location.CountryOrRegion Appname = $Rec.AppDisplayName Resource = $Rec.ResourceDisplayName Status = $Status Correlation = $Rec.CorrelationId Interactive = $Rec.IsInteractive } $Report.Add($ReportLine) } # 处理后续分页 while ($null -ne $page.NextLink) { Start-Sleep -Seconds 2 # 翻页前短休眠控频 $page = Get-AzureADAuditSignInLogs -NextLink $page.NextLink foreach ($Rec in $page) { switch ($Rec.Status.ErrorCode) { "0" { $Status = "Success" } default { $Status = $Rec.Status.FailureReason } } $ReportLine = [PSCustomObject] @{ TimeStamp = Get-Date($Rec.CreatedDateTime) -Format g User = $Rec.UserPrincipalName Name = $Rec.UserDisplayName IPAddress = $Rec.IpAddress ClientApp = $Rec.ClientAppUsed Device = $Rec.DeviceDetail.OperatingSystem Location = $Rec.Location.City + ", " + $Rec.Location.State + ", " + $Rec.Location.CountryOrRegion Appname = $Rec.AppDisplayName Resource = $Rec.ResourceDisplayName Status = $Status Correlation = $Rec.CorrelationId Interactive = $Rec.IsInteractive } $Report.Add($ReportLine) } } $windowSuccess = $true } catch { $retryCount++ # 优先读取Retry-After头的等待时长,读不到默认等60秒 $retryAfter = 60 if ($_.Exception.Response.Headers -and $_.Exception.Response.Headers.Contains("Retry-After")) { $retryAfter = [int]$_.Exception.Response.Headers.GetValues("Retry-After")[0] } Write-Host "触发节流,等待 $retryAfter 秒后重试,当前第 $retryCount 次重试..." Start-Sleep -Seconds $retryAfter } } # 移动到下一个时间窗口 $currentStart = $windowEnd Start-Sleep -Seconds 1 # 时间窗口之间加短间隔 } Write-Host "处理完成,共拉取到 $($Report.Count) 条登录审计记录。"
内容的提问来源于stack exchange,提问作者Yahtzee
相关产品推荐
相关产品推荐

