微软图形API:令牌未包含权限或权限不可理解

9
我正在使用 Microsoft Graph 并已创建一个从特定用户读取邮件的应用程序。然而,在获取访问令牌并尝试读取邮件文件夹后,我收到了 401 未经授权的回答。详细消息如下:令牌不包含权限,或者无法理解权限。这似乎是一个很明确的消息,但不幸的是我无法找到解决方法。到目前为止,我已经完成以下工作: 权限如下: enter image description here enter image description here - 编写以下代码以获取访问令牌:
 // client_secret retrieved from secure storage (e.g. Key Vault)
 string tenant_id = "xxxx.onmicrosoft.com";
 ConfidentialClientApplication client = new ConfidentialClientApplication(
 "..",
 $"https://login.microsoftonline.com/{tenant_id}/",
 "https://dummy.example.com", // Not used, can be any valid URI 
 new ClientCredential(".."),
 null, // Not used for pure client credentials
 new TokenCache());
   string[] scopes = new string[] { "https://graph.microsoft.com/.default" };
   AuthenticationResult result = client.AcquireTokenForClientAsync(scopes).Result;
   string token = result.AccessToken;

到目前为止一切顺利,我已经获得了一个令牌。
现在我想读取邮件文件夹:
url = "https://graph.microsoft.com/v1.0/users/{username}/mailFolders";
handler = (HttpWebRequest)WebRequest.Create(url);
handler.Method = "GET";
handler.ContentType = "application/json";
handler.Headers.Add("Authorization", "Bearer " + token);
response = (HttpWebResponse)handler.GetResponse();
using (StreamReader sr = new StreamReader(response.GetResponseStream()))
{
    returnValue = sr.ReadToEnd();
}
这次我收到了一个401错误信息,详情如下:令牌不包含任何权限,或者权限无法理解。我在互联网上搜索过,但找不到为什么我的令牌没有权限的答案。感谢您的时间! 更新1 如果我使用Graph Explorer来读取邮件文件夹,那么它可以正常工作。此外:如果我从浏览器中获取令牌ID并在我的第二段代码中使用它,那么也会得到结果。因此,问题确实是我从第一步收到的令牌。

您可以将令牌粘贴到 https://jwt.io 中。在那里,您可以检查有效载荷是否真正列出了所需的权限。否则,请重新检查您授予的权限,并可能重新运行应用程序的管理员同意。 - Oliver
嗨,Oliver,感谢您的回复。我已经将其复制粘贴到验证器中。解码后的响应似乎没问题,但老实说,我不知道在哪里查找权限。 - Roeland
如果您查看解码的有效负载,会发现一些常见的JWT属性,例如iatnbfexp。再往下一点,有一个名为scp的属性。它包含了您可以访问的所有范围。这是一个以空格分隔的列表,应该包含文本Mail.ReadMail.ReadWrite。如果不是这种情况,那么您没有权限。 - Oliver
那么这可能就是问题所在:我根本没有scp属性。 - Roeland
那么,您请求 graph.microsoft.com 令牌的方式可能有问题。我不知道是什么问题,但我请求应用程序令牌与您不同 - Oliver
1个回答

5
为确保其按照您的期望运行,您应明确说明要获取访问令牌的租户。 (当然,在此租户中,应用程序应已获得管理员同意。)

请使用特定于租户的端点,而不是“常见”令牌端点:

string url = "https://login.microsoftonline.com/{tenant-id}/oauth2/v2.0/token";

(其中{tenant-id}是租户ID(GUID)或任何已验证的域名。)

我强烈建议不要像你在问题中展示的那样自己构建令牌请求。这可能对教育目的有用,但从长远来看很容易出现安全问题和错误。

相反,您可以使用各种库来完成这项工作。以下是使用Microsoft身份验证库(MSAL).NET版的示例:

// client_secret retrieved from secure storage (e.g. Key Vault)
string tenant_id = "contoso.onmicrosoft.com";
ConfidentialClientApplication client = new ConfidentialClientApplication(
    client_id,
    $"https://login.microsoftonline.com/{tenant_id}/",
    "https://dummy.example.com", // Not used, can be any valid URI 
    new ClientCredential(client_secret),
    null, // Not used for pure client credentials
    new TokenCache());

string[] scopes = new string[] { "https://graph.microsoft.com/.default" };
AuthenticationResult result = client.AcquireTokenForClientAsync(scopes).Result
string token = result.AccessToken;
// ... use token

嗨,Philippe,谢谢你的回答。我已经实现了这段代码,但是在使用令牌请求邮件API时,我仍然收到相同的错误响应。我确信注册应用程序时一定出了问题。我点赞了你的回答,因为我很感激你提供的库建议和努力! - Roeland
你确定权限已经被授予了吗?你可以在Azure门户下确认:企业应用程序 > (找到你的应用程序)> 权限。另外,请更新你的问题,以便它与你最新版本的代码匹配。 - Philippe Signoret
我刚刚在问题中添加了权限。这些权限我可以在apps.dev.microsoft.com(第一张图片)和portal.azure.com(第二张图片)上看到。 - Roeland
1
你似乎仍在使用 common 终结点获取令牌 (https://login.microsoftonline.com/common/),而不是租户特定的终结点 (https://login.microsoftonline.com/{tenant-id})。 你试过用租户特定的终结点吗?(请将 $"https://login.microsoftonline.com/common/", 这一行替换为 $"https://login.microsoftonline.com/{tenant_id}/",,就像我的示例中那样。) - Philippe Signoret
抱歉,你是对的。我确实更改了租户ID,但忘记在URL中使用这个变量。然而结果是一样的。 - Roeland

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接