在守护程序应用程序中使用 MS Graph 需要 Azure AD 中的应用程序类型权限

Using MS Graph in daemon apps requires Application type permissions in Azure AD

examples from here 需要应用程序类型权限,这意味着此类应用程序将有权访问租户中的所有邮箱。这基本上意味着它不能只是任何租户,因为在正常情况下,访问所有邮箱是禁忌。

也就是说,好像只对专门处理某些邮件的租户有用,不包含任何普通个人的邮件。

问题:也许有什么方法可以 运行 只为应用程序配置委派权限的守护进程?这似乎解决了困境。

一种方法是使用应用程序访问策略限制 Exchange Online 中守护程序应用程序的权限:https://docs.microsoft.com/en-us/graph/auth-limit-mailbox-access

另一种更复杂的方法是通过刷新令牌使用委派权限。 该过程如下所示:

  1. 想要授予该应用访问权限以从其邮箱发送电子邮件的用户登录该应用(您需要某种前端)
  2. 应用程序接收 ID 令牌、访问令牌和刷新令牌
  3. ID 令牌告诉应用程序用户是谁,因此他们可以从那里获取例如用户唯一的不可变 ID(oid 声明),以便识别授予访问权限的用户
  4. 访问令牌在这里并不是必需的,因此应用程序可以丢弃它
  5. 另一方面,刷新令牌存储在秘密存储中
  6. Daemon 应用程序想要发送电子邮件,因此它从秘密存储中获取刷新令牌
  7. 使用刷新令牌从 Azure AD 获取新的访问令牌和刷新令牌
  8. 新的刷新令牌存储在秘密存储中以替换现有的
  9. 守护程序应用程序使用访问令牌以用户身份发送电子邮件

守护程序应用程序需要为不可避免的刷新令牌不起作用的情况做好准备。 它可能由于各种原因而过期。 在这种情况下,需要为该用户重复第一步。

如果不使用,刷新令牌也会过期,尽管最近这可能已经改变。 过去我们制定了一个流程,确保每个刷新令牌每周至少用于获取新令牌一次以保持它们新鲜。

因此您可以看到具有应用程序权限的守护程序应用程序方法更加简单和强大(只需确保 secret/certificate 不会过期)。 另一方面,具有委托权限的方法从根本上来说更安全,因为您无法获得比用户拥有的更多的访问权限。 个人用户也可以撤销同意。

如果您只是想读取一个特定 AD 用户的邮箱,您可以使用集成 Windows 提供程序,它使用委托权限。虽然这对 desktop/mobile 应用程序意味着更多,但它也可以用于守护程序应用程序。它的好处是它允许您静默获取令牌,无需用户交互。然而,有一些警告限制了它的范围:

  • 应用程序必须运行作为您尝试访问其邮箱的用户。
  • 用户无法启用 MFA(因为这需要用户交互——对于服务帐户用户来说可能不是问题)。
  • 您需要在 Azure 门户中启用“允许 public 客户端流”应用程序设置。

代码将如下所示。更多信息 here and here.

AuthenticationConfig config = AuthenticationConfig.ReadFromJsonFile("appsettings.json");

IPublicClientApplication app = PublicClientApplicationBuilder
    .Create(config.ClientId)
    .WithTenantId(config.Tenant)
    .Build();

string[] scopes = new string[] { $"{config.ApiUrl}.default" };

var authProvider = new Microsoft.Graph.DelegateAuthenticationProvider(async (request) => {
    var result = await app.AcquireTokenByIntegratedWindowsAuth(scopes).ExecuteAsync();

    request.Headers.Authorization =
        new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", result.AccessToken);
});

var graphClient = new Microsoft.Graph.GraphServiceClient(authProvider);

var messages = await graphClient.Users["TheUser@YourDomain.com"].Messages.Request()
    .Select("sender,subject").GetAsync();  // substitute your user