You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

MailKit使用OAuth2客户端凭证流发送Office365邮件认证失败求助

切换到client_credentials流发送O365 SMTP邮件时认证失败的解决思路

微软即将移除SMTP基本认证支持,我正在为后端应用寻找非交互式邮件发送方案。使用密码授权流时可正常发信,但该方案属于遗留机制且会暴露用户密码,代码如下:

static async Task<string> GetAccessToken(FormUrlEncodedContent content, string tokenEndpoint)
{
    var client = new HttpClient();
    var response = await client.PostAsync(tokenEndpoint, content).ConfigureAwait(continueOnCapturedContext: false);
    var jsonString = await response.Content.ReadAsStringAsync();
    client.Dispose();

    var doc = JsonDocument.Parse(jsonString);
    JsonElement root = doc.RootElement;
    if (root.TryGetProperty("access_token", out JsonElement tokenElement))
        return tokenElement.GetString()!;

    throw new Exception("Failed to get access token");
}

static void SendO365(SaslMechanism accessToken, string host, int port, string from, string to)
{
    using (var client = new SmtpClient())
    {
        client.ServerCertificateValidationCallback = (s, c, h, e) => true;

        try
        {
            client.Connect(host, port, SecureSocketOptions.Auto);
            client.Authenticate(accessToken);
            var msg = new MimeMessage();
            msg.From.Add(MailboxAddress.Parse(from));
            msg.To.Add(MailboxAddress.Parse(to));
            msg.Subject = "Testing SMTP";
            msg.Body = new TextPart("plain") { Text = "This is a test message." };
            client.Send(msg);
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }
}

var content = new FormUrlEncodedContent(new List<KeyValuePair<string, string>>
{
    new KeyValuePair<string, string>("client_id", "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxx"),
    new KeyValuePair<string, string>("client_secret", "yyy"),
    new KeyValuePair<string, string>("grant_type", "password"),
    new KeyValuePair<string, string>("resource", "https://outlook.office365.com"),
    new KeyValuePair<string, string>("scope", ".default"),
    new KeyValuePair<string, string>("username", "foo@domain.com"),
    new KeyValuePair<string, string>("password", "zzz"),
});

string tenantId = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxx";
string tokenEndpoint = $"https://login.microsoftonline.com/{tenantId}/oauth2/token";
var accessToken = GetAccessToken(content, tokenEndpoint).Result;

var userEmail = "foo@domain.com";
var smtpServer = "smtp.office365.com";
var smtpPort = 587;
var toEmail = "foo2@domain.com";

SendO365(new SaslMechanismOAuth2(userEmail, accessToken), smtpServer, smtpPort, userEmail, toEmail);

我已在Entra中配置了Mail.Send权限,但切换到更安全的client_credentials流时,client.Authenticate(accessToken)步骤始终报错Authentication unsuccessful,以下是针对性解决思路:


核心问题排查与修正步骤

  1. 确认权限类型为应用权限
    必须在Entra中配置应用权限类型的Mail.Send,而非委派权限。委派权限仅适用于用户交互场景,client_credentials流以应用身份运行,只能使用应用权限,且需完成全局管理员同意操作。

  2. 调整令牌请求参数
    将密码流的请求参数替换为client_credentials流格式:

    var content = new FormUrlEncodedContent(new List<KeyValuePair<string, string>>
    {
        new KeyValuePair<string, string>("client_id", "你的客户端ID"),
        new KeyValuePair<string, string>("client_secret", "你的客户端密钥"),
        new KeyValuePair<string, string>("grant_type", "client_credentials"),
        new KeyValuePair<string, string>("scope", "https://outlook.office365.com/.default"),
    });
    // 使用v2版本令牌端点(更规范,兼容应用权限)
    string tokenEndpoint = $"https://login.microsoftonline.com/{tenantId}/oauth2/v2.0/token";
    
    • 移除username、password参数
    • 将scope设为https://outlook.office365.com/.default,确保令牌包含正确的资源权限
  3. 验证令牌有效性
    拿到令牌后,用jwt.ms解析确认:

    • aud字段值为https://outlook.office365.com
    • roles字段包含Mail.Send(代表应用权限已生效)
      若缺少roles字段,说明权限未完成管理员同意或配置错误。
  4. SMTP认证与发送配置

    • SaslMechanismOAuth2的用户名参数需填写要发送的邮箱地址(如foo@domain.com)
    • 确保Exchange Online中已给应用配置该邮箱的Send As权限(在Exchange admin中心的邮箱权限设置中添加应用)
    • 生产环境需移除ServerCertificateValidationCallback = (s, c, h, e) => true,保留默认证书验证逻辑
  5. 检查租户安全策略
    确认租户未限制应用通过SMTP发送邮件,可在Exchange admin中心的安全策略中排查相关限制。


内容的提问来源于stack exchange,提问作者Tom el Safadi

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.23 00:35:02