使用Microsoft Graph API在Windows PowerShell发邮件遇400错误求助
解决Microsoft Graph API发送邮件400错误及凭据安全优化
排查400 Bad Request错误
400错误通常源于请求格式或配置问题,按以下要点逐一排查:
- 请求体JSON格式错误:Graph API对邮件请求体的嵌套结构要求严格,需确保层级正确,且使用
ConvertTo-Json -Depth 3(默认深度不足会截断嵌套内容)。示例正确请求体:
$mailBody = @{ message = @{ subject = "测试邮件" body = @{ contentType = "Text" # 或"HTML" content = "这是测试邮件内容" } toRecipients = @( @{ emailAddress = @{ address = "recipient@example.com" } } ) } saveToSentItems = $true } | ConvertTo-Json -Depth 3
- 端点URL匹配权限类型:委派权限需使用
https://graph.microsoft.com/v1.0/me/sendMail,应用权限才用/users/{user-id}/sendMail,用错端点会直接返回400。 - 令牌权限范围验证:解码获取到的令牌(可本地用
jwt.ms解析),确认包含https://graph.microsoft.com/Mail.Send范围,若缺失则重新获取令牌时需指定正确scope。 - 特殊字符转义:邮件主题或内容含特殊字符时,依赖
ConvertTo-Json自动转义,避免手动拼接JSON导致格式错误。
优化凭据安全性
1. 避免明文存储凭据
绝对不要在脚本中硬编码账号密码,推荐两种安全方式:
- 交互式获取凭据:
$cred = Get-Credential -Message "输入Azure管理员账号凭据"
- 加密存储到Windows凭据管理器:
# 首次存储 $cred = Get-Credential $cred | Export-Clixml -Path "$env:USERPROFILE\graph_cred.xml" # 后续读取(仅当前用户可解密) $storedCred = Import-Clixml -Path "$env:USERPROFILE\graph_cred.xml"
2. 使用设备码流替代密码流
密码流安全性低,设备码流无需输入密码,适合本地脚本:
$clientId = "你的Azure AD应用ID" $tenantId = "你的租户ID" $scopes = "https://graph.microsoft.com/Mail.Send" # 获取设备码 $deviceCodeRequest = Invoke-RestMethod -Uri "https://login.microsoftonline.com/$tenantId/oauth2/v2.0/devicecode" -Method Post -Body @{ client_id = $clientId scope = $scopes } Write-Host "请在浏览器打开 $($deviceCodeRequest.verification_uri),输入代码 $($deviceCodeRequest.user_code) 完成登录" # 轮询获取令牌 do { Start-Sleep -Seconds 5 $tokenResponse = try { Invoke-RestMethod -Uri "https://login.microsoftonline.com/$tenantId/oauth2/v2.0/token" -Method Post -Body @{ client_id = $clientId grant_type = "urn:ietf:params:oauth:grant-type:device_code" device_code = $deviceCodeRequest.device_code } } catch { $null } } while (-not $tokenResponse)
3. 遵循最小权限原则
仅为应用配置必要权限,比如只需要发送邮件就保留Mail.Send,移除不必要的Mail.ReadWrite权限,降低风险。
完整可测试脚本
# 配置参数 $clientId = "你的Azure AD应用ID" $tenantId = "你的租户ID" $recipientEmail = "收件人邮箱" $subject = "Graph API测试邮件" $content = "这是通过Microsoft Graph API发送的测试邮件" # 设备码流获取令牌 $deviceCodeRequest = Invoke-RestMethod -Uri "https://login.microsoftonline.com/$tenantId/oauth2/v2.0/devicecode" -Method Post -Body @{ client_id = $clientId scope = "https://graph.microsoft.com/Mail.Send" } Write-Host "请在浏览器打开 $($deviceCodeRequest.verification_uri),输入代码 $($deviceCodeRequest.user_code) 完成登录" do { Start-Sleep -Seconds 5 $tokenResponse = try { Invoke-RestMethod -Uri "https://login.microsoftonline.com/$tenantId/oauth2/v2.0/token" -Method Post -Body @{ client_id = $clientId grant_type = "urn:ietf:params:oauth:grant-type:device_code" device_code = $deviceCodeRequest.device_code } } catch { $null } } while (-not $tokenResponse) # 构建邮件请求体 $mailBody = @{ message = @{ subject = $subject body = @{ contentType = "Text" content = $content } toRecipients = @( @{ emailAddress = @{ address = $recipientEmail } } ) } saveToSentItems = $true } | ConvertTo-Json -Depth 3 # 发送邮件 try { Invoke-RestMethod -Uri "https://graph.microsoft.com/v1.0/me/sendMail" -Method Post -Headers @{ Authorization = "Bearer $($tokenResponse.access_token)" Content-Type = "application/json" } -Body $mailBody Write-Host "邮件发送成功" } catch { Write-Host "发送失败:$($_.Exception.Message)" if ($_.ErrorDetails) { Write-Host "详细错误:$($_.ErrorDetails.Message)" } }
内容的提问来源于stack exchange,提问作者Sachin Tayade
相关产品推荐
相关产品推荐

