找到了有关使用 Microsoft graph 发送电子邮件的问题和文章。在 Azure 上,我们注册了应用程序,并且添加了 Mail.Send 作为应用程序权限,如文章中所述,并且代码有效。但是,根据我的理解,该代码可以使用任何用户的电子邮件。我们想要使用身份验证代码流程,如下面的代码所示,因此我们有一个注册用户。然而,我们得到了禁止的回应。我们是否做错了什么,或者身份验证代码流程是否不适用于图形发送电子邮件。
public static string ClientId = "ClientId Here";
public static string TenantId = "TenantId Here";
public static string RedirectUri = "http://localhost;
public static string AppAuthority = $"https://login.microsoftonline.com/{TenantId}/v2.0";
_clientApp = PublicClientApplicationBuilder.Create ( ClientId )
.WithAuthority ( AppAuthority )
.WithRedirectUri ( RedirectUri )
.Build ();
TokenCacheHelper.EnableSerialization ( _clientApp.UserTokenCache );
// Set up the Microsoft Graph API endpoint and version
string graphApiEndpoint = "https://graph.microsoft.com/v1.0";
AuthenticationResult authResult = null;
var app = _clientApp;
List<string> scope =
new List<string> ()
{
"https://graph.microsoft.com/.default"
};
IAccount firstAccount;
var accounts = await app.GetAccountsAsync ();
firstAccount = accounts.FirstOrDefault ();
try
{
authResult = await app.AcquireTokenSilent ( scope, firstAccount )
.ExecuteAsync ();
}
catch ( MsalUiRequiredException ex )
{
try
{
authResult = await app.AcquireTokenInteractive ( scope )
.WithAccount ( firstAccount )
.WithPrompt ( Prompt.SelectAccount )
.ExecuteAsync ();
}
catch ( MsalException msalex )
{}
}
catch ( Exception ex )
{
return;
}
if ( authResult == null )
return;
// Set up the user's email address and message content
string userEmail = "[email protected]";
string messageSubject = "Test email";
string messageBody = "This is a test email sent via Microsoft Graph";
// Set up the HTTP client and add the access token to the authorization header
var httpClient = new HttpClient ();
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue ( "Bearer", authResult.AccessToken );
// Set up the email message
var emailMessage = new
{
message = new
{
subject = messageSubject,
body = new
{
contentType = "Text",
content = messageBody
},
toRecipients = new []
{
new
{
emailAddress = new
{
address = userEmail
}
}
}
}
};
// Convert the email message to a JSON string and send the email via Microsoft Graph
var options = new JsonSerializerOptions { WriteIndented = true };
string jsonMessage = JsonSerializer.Serialize ( emailMessage, options );
var response = await httpClient.PostAsync ( $"{graphApiEndpoint}/users/{userEmail}/sendMail", new StringContent ( jsonMessage, System.Text.Encoding.UTF8, "application/json" ) );
if ( response.IsSuccessStatusCode )
{
Console.WriteLine ( "Email sent successfully." );
}
else
{
Console.WriteLine ( "Failed to send email. Status code: " + response.StatusCode );
}
using System;
using System.IO;
using System.Security.Cryptography;
using Microsoft.Identity.Client;
namespace WpfApp1
{
static class TokenCacheHelper
{
static TokenCacheHelper ()
{
try
{
string folder = Environment.GetFolderPath ( Environment.SpecialFolder.ApplicationData );
// Combine the base folder with your specific folder....
string localFolder = Path.Combine ( folder, "Local" );
// CreateDirectory will check if every folder in path exists and, if not, create them.
// If all folders exist then CreateDirectory will do nothing.
Directory.CreateDirectory ( localFolder );
var localAppData = Environment.GetFolderPath ( Environment.SpecialFolder.LocalApplicationData );
// For packaged desktop apps (MSIX packages, also called desktop bridge) the executing assembly folder is read-only.
// In that case we need to use Windows.Storage.ApplicationData.Current.LocalCacheFolder.Path + "\msalcache.bin"
// which is a per-app read/write folder for packaged apps.
// See https://docs.microsoft.com/windows/msix/desktop/desktop-to-uwp-behind-the-scenes
CacheFilePath = Path.Combine ( localAppData, ".msalcache.bin3" );
}
catch ( System.InvalidOperationException )
{
// Fall back for an unpackaged desktop app
CacheFilePath = System.Reflection.Assembly.GetExecutingAssembly ().Location + ".msalcache.bin3";
}
}
/// <summary>
/// Path to the token cache
/// </summary>
public static string CacheFilePath { get; private set; }
private static readonly object FileLock = new object ();
public static void BeforeAccessNotification ( TokenCacheNotificationArgs args )
{
lock ( FileLock )
{
args.TokenCache.DeserializeMsalV3 ( File.Exists ( CacheFilePath )
? ProtectedData.Unprotect ( File.ReadAllBytes ( CacheFilePath ),
null,
DataProtectionScope.CurrentUser )
: null );
}
}
public static void AfterAccessNotification ( TokenCacheNotificationArgs args )
{
// if the access operation resulted in a cache update
if ( args.HasStateChanged )
{
lock ( FileLock )
{
// reflect changes in the persistent store
File.WriteAllBytes ( CacheFilePath,
ProtectedData.Protect ( args.TokenCache.SerializeMsalV3 (),
null,
DataProtectionScope.CurrentUser )
);
}
}
}
internal static void EnableSerialization ( ITokenCache tokenCache )
{
tokenCache.SetBeforeAccess ( BeforeAccessNotification );
tokenCache.SetAfterAccess ( AfterAccessNotification );
}
}
}
注意:您可以通过授权码流程发送邮件。但此流程需要委派权限而不是应用程序权限。因此,向 Azure AD 应用程序授予
委派 API 权限。Mail.send
创建 Azure Ad 应用程序并授予
Mail.send
委派 Api 权限:
现在我使用以下端点生成了auth-code:
https://login.microsoftonline.com/TenantID/oauth2/v2.0/authorize?
&client_id=ClientID
&response_type=code
&redirect_uri=https://jwt.ms
&response_mode=query
&scope=https://graph.microsoft.com/.default
&state=12345
使用以下参数通过 Postman 生成访问令牌:
https://login.microsoftonline.com/TenantID/oauth2/v2.0/token
client_id:ClientID
scope:https://graph.microsoft.com/.default
grant_type:authorization_code
code:code
redirect_uri:https://jwt.ms
client_secret:Secret
确保解码访问令牌并检查范围是否显示:
现在通过使用上面的访问令牌,我能够成功发送邮件:
POST https://graph.microsoft.com/v1.0/me/sendMail
Content-type: application/json
{
"message": {
"subject": "Meet for lunch?",
"body": {
"contentType": "Text",
"content": "The new cafeteria is open."
},
"toRecipients": [
{
"emailAddress": {
"address": "xxx.onmicrosoft.com"
}
}
],
"ccRecipients": [
{
"emailAddress": {
"address": "xxx.onmicrosoft.com"
}
}
]
},
"saveToSentItems": "false"
}
参考: