Сервис Framework.net, использующий ConfidentialClientApplication и REST API задач Outlook, получил StatusCode: 401, ReasonPhrase: «Не авторизован» - PullRequest
2 голосов
/ 24 июня 2019

Hello World!

Привет всем!Я новичок в StackOverflow, поэтому я приложу все усилия, чтобы лучше объяснить мою проблему.

Проблема

Я пытаюсь взаимодействовать с Outlook Calendar и Tasks REST API из ac #.NET Framework Cli Program, которая станет фоновой службой Windows.

Я создал выделенную учетную запись Outlook, зарегистрировал приложение на портале Azure и адаптировал rest-sender проект.

С помощью PublicClientApplicationBuilder.AcquireTokenInteractive я могу получить действительный, хотя и временный (1 час), токен доступа и список моих задач.Он работает и для других вызовов API, но проблема в том, что каждый раз, когда мне требуется токен, он показывает и интерактивное окно веб-браузера для получения учетной записи.Приложение станет фоновой службой Windows на производственном головном сервере, поэтому интерактивное окно невозможно.

Я обнаружил ConfidentialClientApplication.AcquireTokenForClient, для которого требуется секрет клиента.Я создал один из портала Azure.Я могу получить токен, но кажется, что он не авторизован.

Портал

приложение Это экран приложения на странице Azure.

auth Это настройки аутентификации.

auths Это настройки авторизации.

secret Это секретная страница.

почта Из почты я вижу эти разрешения (предоставленные с GetInteractiveToken)

токен Этоэто декодированный токен.


Это иллюстративный код:


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
}}

Оба GetSecretAccessToken и GetInteractiveAccessToken предоставляют токен, но только интерактивный работает в методе ListTasks.Другой нужен для работы с сервисом Win.

Я прочитал, что https://outlook.office.com/.default заставляет приложение использовать статические разрешения приложения, которые я установил на портале Azure.Но я не могу заставить этот метод работать.

Я просто не знаю, какое разрешение требуется для работы в этом режиме.

Я почти неделю пытаюсьзаставить это работать, без успеха.Может быть, я упускаю что-то тривиальное ... Поэтому я ищу помощь в лучшем месте на планете.

Я также нашел пример в ядре .net, который регистрирует код устройства, но я предпочитаю остатьсяс базовой платформой .net, потому что у меня уже есть работающий проект в моей клиентской системе.

Я надеюсь, что прояснил свою проблему, и это возможно.Было бы очень хорошо иметь интеграцию с Outlook.

Заранее спасибо .

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

Tl; Dr;

Платформа идентификации Microsoft не поддерживает личные учетные записи, поэтому, если вы хотите получить доступ к API REST outlook365.com из фоновой службы / демона, вам нужно быть под Azure AD.и должен быть одобрен администратором (он нажмет кнопку «Предоставить согласие администратора для [себя]»).В противном случае вы не сможете выполнить какой-либо запрос, поскольку ваш неинтерактивный токен не будет содержать никаких разрешений на область действия.

Спасибо Джейсону Джонстону за большую помощь!

1 Ответ

1 голос
/ 24 июня 2019

Вы используете поток учетных данных клиента для получения токена приложения. Этот токен не имеет пользовательского контекста, поэтому вы не можете использовать сегмент /me в URL вашего запроса. Замените это на /users/{user-id}. {user-id} - это либо идентификатор объекта пользователя (полученный с помощью вызова GET /users), либо его UPN (обычно это адрес электронной почты).

Я бы также порекомендовал скопировать токен и проанализировать его на https://jwt.ms.. Вы хотите просмотреть токен и убедиться, что он содержит разрешение Tasks.Read.

Также убедитесь, что вы получили согласие администратора на разрешения приложения, которые вы настроили при регистрации приложения. Если вы являетесь администратором, вы должны увидеть раздел Предоставить согласие прямо под разделом Разрешения API с кнопкой, которую вы можете нажать, чтобы дать согласие.

enter image description here

Личные счета

Если вы пишете это для доступа к личной учетной записи Outlook.com, вы не можете использовать поток учетных данных клиента. Этот конкретный поток доступен только для рабочих или школьных учетных записей в Office 365.

Azure OAuth в настоящее время не поддерживает неинтерактивные потоки для личных учетных записей. Самое близкое, что вы можете получить, это реализовать сериализацию кэша токена , а затем выполнить интерактивный вход в систему с приложением один раз (чтобы заполнить кэш с помощью токена обновления пользователя). Затем при последующих запусках вы можете позвонить AcquireTokenSilent.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...