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,以下是针对性解决思路:
核心问题排查与修正步骤
确认权限类型为应用权限
必须在Entra中配置应用权限类型的Mail.Send,而非委派权限。委派权限仅适用于用户交互场景,client_credentials流以应用身份运行,只能使用应用权限,且需完成全局管理员同意操作。调整令牌请求参数
将密码流的请求参数替换为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,确保令牌包含正确的资源权限
- 移除
验证令牌有效性
拿到令牌后,用jwt.ms解析确认:aud字段值为https://outlook.office365.comroles字段包含Mail.Send(代表应用权限已生效)
若缺少roles字段,说明权限未完成管理员同意或配置错误。
SMTP认证与发送配置
SaslMechanismOAuth2的用户名参数需填写要发送的邮箱地址(如foo@domain.com)- 确保Exchange Online中已给应用配置该邮箱的
Send As权限(在Exchange admin中心的邮箱权限设置中添加应用) - 生产环境需移除
ServerCertificateValidationCallback = (s, c, h, e) => true,保留默认证书验证逻辑
检查租户安全策略
确认租户未限制应用通过SMTP发送邮件,可在Exchange admin中心的安全策略中排查相关限制。
内容的提问来源于stack exchange,提问作者Tom el Safadi
相关产品推荐
相关产品推荐

