PowerShell中使用证书认证Microsoft Graph API遇AADSTS50027错误
解决PowerShell 5.1中JWT断言无效的问题
你的脚本生成的JWT不符合Azure AD的要求,导致返回AADSTS50027错误,以下是具体问题和修正方案:
核心问题分析
- JWT结构不完整:标准JWT由
Header.Payload.Signature三部分组成,原脚本仅生成了Payload和Signature,缺少必须包含算法与类型声明的Header部分。 - Base64编码不符合规范:JWT要求使用URL安全的Base64编码(替换
+为-、/为_,移除末尾=),原脚本使用普通Base64会导致令牌格式错误。 - 私钥处理不当:PowerShell 5.1中
$cert.PrivateKey可能返回RSACng对象,直接用RSACryptoServiceProvider导入参数会引发兼容性问题。 - Audience参数错误:v2.0令牌端点的
aud值应为https://login.microsoftonline.com/{tenantId}/v2.0,而非带租户域名的格式。 - 手动拼接请求体易出错:手动构建URL编码的请求体可能导致参数编码错误,应直接传递哈希表给
Invoke-RestMethod。
修正后的完整脚本
$thumbprint = "<证书指纹>" $clientId = "<客户端ID>" $tenantId = "<租户ID>" # 1. 生成JWT Header(必须包含alg和typ) $header = @{ alg = "RS256" typ = "JWT" } # 2. 生成JWT Payload $expUnixTime = [math]::truncate(((Get-Date).AddMinutes(30).ToUniversalTime()).Subtract((Get-Date "1970-01-01").ToUniversalTime()).TotalSeconds) $payload = @{ iss = $clientId # 发行者应为客户端ID,而非证书CN sub = $clientId # 主体与发行者一致,应为客户端ID aud = "https://login.microsoftonline.com/$tenantId/v2.0" # 正确的v2.0端点aud值 exp = $expUnixTime nbf = [math]::truncate(((Get-Date).ToUniversalTime()).Subtract((Get-Date "1970-01-01").ToUniversalTime()).TotalSeconds) # 添加生效时间 } # 3. 实现URL安全的Base64编码函数 function ConvertTo-Base64Url { param([string]$InputString) $bytes = [System.Text.Encoding]::UTF8.GetBytes($InputString) $base64 = [Convert]::ToBase64String($bytes) # 替换为URL安全字符并移除末尾等号 return $base64.Replace('+', '-').Replace('/', '_').TrimEnd('=') } # 4. 编码Header和Payload $encodedHeader = ConvertTo-Base64Url ($header | ConvertTo-Json -Compress) $encodedPayload = ConvertTo-Base64Url ($payload | ConvertTo-Json -Compress) # 5. 准备签名的原始数据(Header.Payload) $signatureInput = "$encodedHeader.$encodedPayload" # 6. 使用证书私钥签名 $cert = Get-Item -Path Cert:\CurrentUser\My\$thumbprint $rsa = $cert.PrivateKey if ($rsa -is [System.Security.Cryptography.RSACng]) { $rsa = [System.Security.Cryptography.RSA]::Create($rsa.ExportParameters($true)) } $signatureBytes = $rsa.SignData([System.Text.Encoding]::UTF8.GetBytes($signatureInput), [System.Security.Cryptography.HashAlgorithmName]::SHA256, [System.Security.Cryptography.RSASignaturePadding]::Pkcs1) $encodedSignature = ConvertTo-Base64Url $signatureBytes # 7. 构建完整JWT断言 $jwtAssertion = "$encodedHeader.$encodedPayload.$encodedSignature" # 8. 请求令牌 $tokenUrl = "https://login.microsoftonline.com/$tenantId/oauth2/v2.0/token" $body = @{ client_id = $clientId tenant_id = $tenantId client_assertion_type = "urn:ietf:params:oauth:client-assertion-type:jwt-bearer" client_assertion = $jwtAssertion scope = "https://graph.microsoft.com/.default" grant_type = "client_credentials" } try { # 直接传递哈希表,PowerShell自动处理URL编码 $response = Invoke-RestMethod -Uri $tokenUrl -Method Post -Body $body -ContentType "application/x-www-form-urlencoded" $response } catch { Write-Error "错误: $($_.Exception.Message)" # 输出详细错误响应(如果有) if ($_.Exception.Response) { $reader = New-Object System.IO.StreamReader($_.Exception.Response.GetResponseStream()) $errorResponse = $reader.ReadToEnd() Write-Error "详细错误响应: $errorResponse" } }
关键修正说明
- 添加JWT Header:明确指定签名算法为
RS256,类型为JWT,这是Azure AD要求的必填项。 - 修正Payload字段:
iss和sub应为客户端ID(而非证书CN),aud使用v2.0端点的正确格式,添加nbf(生效时间)字段增强合规性。 - URL安全Base64编码:实现专用函数处理JWT所需的编码格式,避免普通Base64的特殊字符导致令牌解析失败。
- 兼容私钥类型:处理
RSACng类型的私钥,确保在PowerShell 5.1中正常签名。 - 简化请求体构建:直接传递哈希表给
Invoke-RestMethod,避免手动拼接的编码错误。
内容的提问来源于stack exchange,提问作者StackExchangeGuy
相关产品推荐
相关产品推荐

