Framework.net 服务,使用 ConfidentialClientApplication 和 Outlook 任务 REST API,得到 StatusCode:401,ReasonPhrase:'Unauthorized'

Framework.net service, using ConfidentialClientApplication and the Outlook Tasks REST API, got StatusCode: 401, ReasonPhrase: 'Unauthorized'

你好世界!

大家好! 我是 Whosebug 的新手,所以我会尽力更好地解释我的问题。

问题

我正尝试从 c# .net 框架 cli 程序与 Outlook 日历和任务 REST API 交互,这将成为后台 Windows 服务。

我已经创建了一个专用的 Outlook 帐户, 在 Azure portal and adapted the rest-sender 项目上注册了该应用程序。

使用 PublicClientApplicationBuilder.AcquireTokenInteractive 我可以获得有效的访问令牌,即使是有效的(1 小时),也可以列出我的任务。 它也适用于其他 API 调用, 但问题是,每次我需要一个令牌时,它都会显示 interactive 网络浏览器 window 来获取一个帐户。 该应用程序将成为生产无外设服务器中的 Windows 后台服务,因此无法进行交互 window。

我发现 ConfidentialClientApplication.AcquireTokenForClient 需要客户端密码。 我已经从 Azure 门户创建了一个。 可以拿到token,但是好像没有授权。

传送门

the app 这是Azure页面中的应用程序屏幕。

auth 这些是身份验证设置。

auths 这些是授权设置。

secret 这是秘密页面。

mail 从邮件中我可以看到这些权限(授予 GetInteractiveToken

token 这是解码后的令牌。


这是说明性代码:


static async Task<string> GetSecretAccessToken()
{
    var client =
        ConfidentialClientApplicationBuilder
            .Create("<applicationId>")
            .WithClientSecret("<clientSecret>")
            .Build();

    var result = client.AcquireTokenForClient(new []{
        "https://outlook.office.com/.default",
    }).ExecuteAsync();

    var r = result.Result.AccessToken;
    return r;
}

static async Task<string> GetInteractiveAccessToken()
{
    var client =
        PublicClientApplicationBuilder
            .Create("<applicationId>")
            .Build();

    var result = client.AcquireTokenInteractive(new []{
        "https://outlook.office.com/Tasks.ReadWrite"
    }).ExecuteAsync();

    var r = result.Result.AccessToken;
    return r;
}

static async Task<string> GetAccessFromPasswordToken(string[] scopes){
            try
            {
                var b =
                    PublicClientApplicationBuilder.Create(ConfigurationManager.AppSettings.Get("applicationId"))
                        .Build();
                var result = b.AcquireTokenByUsernamePassword(scopes, "CodeGen.Preventizzatore@Outlook.it", GetSecureString()).ExecuteAsync();
                return result.Result.AccessToken;
            }
            catch (MsalException ex)
            {
                Output.WriteLine(Output.Error, "Could not acquire access token: Error code: {0}, Error message: ",
                    ex.ErrorCode, ex.Message);
                return string.Empty;
            }
}

static async Task ListTasks()
{
    string token = await GetAccessToken();

    var request = new HttpRequestMessage(
        new HttpMethod("GET"),
        new UriBuilder("https://outlook.office.com/api/v2.0/users/CodeGen.Preventizzatore@Outlook.it/taskfolders").Uri
    );
    request.Headers.Authorization =
        new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
    request.Headers.UserAgent.Add(
        new System.Net.Http.Headers.ProductInfoHeaderValue("rest-sender", "1.0"));
    request.Headers.Accept.Add(
        new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json"));

    var result = await new HttpClient().SendAsync(request);
    if (result.StatusCode == HttpStatusCode.Unauthorized)
        return; // when GetSecretAccessToken
    // when GetInteractiveAccessToken
    string response = await result.Content.ReadAsStringAsync();
}

这是 Azure 上的清单:

{
    "id": "a752666c-11a9-425e-a382-c63ebcbc8bb2",
    "acceptMappedClaims": null,
    "accessTokenAcceptedVersion": 2,
    "addIns": [],
    "allowPublicClient": null,
    "appId": "608bd039-6bc9-4a7e-ad86-2b6a3e20584b",
    "appRoles": [],
    "oauth2AllowUrlPathMatching": false,
    "createdDateTime": "2019-06-21T08:18:56Z",
    "groupMembershipClaims": null,
    "identifierUris": [
        "api://608bd039-6bc9-4a7e-ad86-2b6a3e20584b"
    ],
    "informationalUrls": {
        "termsOfService": null,
        "support": null,
        "privacy": null,
        "marketing": null
    },
    "keyCredentials": [],
    "knownClientApplications": [],
    "logoUrl": null,
    "logoutUrl": null,
    "name": "CodeGen.Preventizzatore",
    "oauth2AllowIdTokenImplicitFlow": false,
    "oauth2AllowImplicitFlow": true,
    "oauth2Permissions": [],
    "oauth2RequirePostResponse": false,
    "optionalClaims": null,
    "orgRestrictions": [],
    "parentalControlSettings": {
        "countriesBlockedForMinors": [],
        "legalAgeGroupRule": "Allow"
    },
    "passwordCredentials": [
        {
            "customKeyIdentifier": null,
            "endDate": "2299-12-30T23:00:00Z",
            "keyId": "c4a2fe3a-a09c-41ff-9115-40a2a2ef1a89",
            "startDate": "2019-06-21T11:46:06.306Z",
            "value": null,
            "createdOn": "2019-06-21T11:46:08.2116173Z",
            "hint": "nR?",
            "displayName": "Preventizzatore"
        }
    ],
    "preAuthorizedApplications": [],
    "publisherDomain": null,
    "replyUrlsWithType": [
        {
            "url": "urn:ietf:wg:oauth:2.0:oob",
            "type": "InstalledClient"
        }
    ],
    "requiredResourceAccess": [
        {
            "resourceAppId": "00000002-0000-0ff1-ce00-000000000000",
            "resourceAccess": [
                {
                    "id": "bbd1ca91-75e0-4814-ad94-9c5dbbae3415",
                    "type": "Scope"
                },
                {
                    "id": "6b49b74d-642f-4417-a6b4-820576845707",
                    "type": "Scope"
                },
                {
                    "id": "bf24470f-10c1-436d-8d53-7b997eb473be",
                    "type": "Role"
                },
                {
                    "id": "77e65b5a-ceae-48b3-9490-50a86a038a48",
                    "type": "Role"
                },
                {
                    "id": "dc890d15-9560-4a4c-9b7f-a736ec74ec40",
                    "type": "Role"
                },
                {
                    "id": "798ee544-9d2d-430c-a058-570e29e34338",
                    "type": "Role"
                },
                {
                    "id": "c1b0de0a-1de9-455d-919f-eca451053141",
                    "type": "Role"
                },
                {
                    "id": "2c6a42ca-0d4d-49ad-bc0e-21222c449a65",
                    "type": "Role"
                },
                {
                    "id": "ef54d2bf-783f-4e0f-bca1-3210c0444d99",
                    "type": "Role"
                },
                {
                    "id": "2dfdc6dc-2fa7-4a2c-a922-dbd4f85d17be",
                    "type": "Role"
                }
            ]
        }
    ],
    "samlMetadataUrl": null,
    "signInUrl": null,
    "signInAudience": "AzureADandPersonalMicrosoftAccount",
    "tags": [],
    "tokenEncryptionKeyId": null
}

使用非交互式令牌时,我得到:

{StatusCode: 401, ReasonPhrase: 'Unauthorized', Version: 1.1, Content: System.Net.Http.StreamContent, Headers:
{
  Transfer-Encoding: chunked
  Cache-Control: private
  WWW-Authenticate: Bearer client_id="00000002-0000-0ff1-ce00-000000000000", trusted_issuers="00000001-0000-0000-c000-000000000000@*", token_types="app_asserted_user_v1 service_asserted_app_v1", authorization_uri="https://login.windows.net/common/oauth2/authorize", error="invalid_token"
  WWW-Authenticate: Basic Realm=""
  request-id: 8a2ccebc-e8c3-4ec2-b8cb-64d1005a5bf7
  X-CalculatedBETarget: MR2P264MB0817.FRAP264.PROD.OUTLOOK.COM
  X-BackEndHttpStatus: 401
  X-RUM-Validated: 1
  x-ms-diagnostics: 2000008;reason="The token contains no permissions, or permissions can not be understood.";error_category="invalid_grant"
  X-AspNet-Version: 4.0.30319
  X-BeSku: WCS5
  X-DiagInfo: MR2P264MB0817
  X-BEServer: MR2P264MB0817
  X-Powered-By: ASP.NET
  X-FEServer: MRXP264CA0011
  X-MSEdge-Ref: Ref A: 25DDA6AB2E72497199D611B555DACB60 Ref B: MIL30EDGE0414 Ref C: 2019-06-24T06:10:44Z
  Date: Mon, 24 Jun 2019 06:10:43 GMT
  Content-Type: text/html; charset=utf-8
}}

GetSecretAccessTokenGetInteractiveAccessToken都提供了一个token,但是只有交互式的在ListTasks方法中有效。 我需要另一个来使用 Win 服务。

我读到 https://outlook.office.com/.default 让应用程序使用我在 Azure 门户上设置的静态应用程序权限。 但是我无法使这种方法起作用。

就是不知道这个模式需要什么样的权限才能工作

我花了将近一个星期的时间来尝试使它正常工作,但没有成功。 也许我错过了一些微不足道的东西...... 所以我在这个星球上最好的地方寻求帮助。

我还在 .net 核心中找到了一个注册设备代码的示例,但我更愿意继续使用基础 .net 框架,因为我的客户系统上已经有一个 运行 项目。

我希望我已经澄清了我的问题并且这是可行的。 如果能集成 Outlook 就好了。

提前致谢

==============

Tl;博士;

Microsoft 身份平台不支持个人帐户,因此如果您想从后台访问 outlook365.com REST API service/daemon,您需要在 Azure AD 下并得到管理员的批准(他将按下“Grant admin consent for [yourself]”按钮)。 否则您将无法执行任何请求,因为您的非交互式令牌将不包含任何范围权限。

感谢 Jason Johnston 的大力帮助!

您正在使用客户端凭据流来获取应用程序令牌。此令牌没有用户上下文,因此您不能在请求 URL 中使用 /me 段。将其替换为 /users/{user-id}{user-id} 是用户的对象 ID(通过执行 GET /users 调用获得)或他们的 UPN(通常是他们的电子邮件地址)。

我还建议复制令牌并在 https://jwt.ms 解析它。您想要查看令牌并确保它包含 Tasks.Read 权限。

另外请确保您已获得管理员对您在应用注册时配置的应用程序权限的同意。如果您是管理员,您应该会在 API 权限 部分下方看到 Grant consent 部分,您可以单击该按钮同意。

个人账户

如果您写这篇文章是为了访问个人 Outlook.com 帐户,则不能使用客户端凭据流。该特定流程仅适用于 Office 365 上的工作或学校帐户。

Azure OAuth 目前不支持个人帐户的 non-interactive 流程。您可以获得的最接近的方法是实现 token cache serialization,然后使用该应用程序进行一次交互式登录(以使用用户的刷新令牌为缓存播种)。然后在后续运行中,您可以调用 AcquireTokenSilent.