Table of Contents

Авторизация в C# через клиентскую библиотеку

В этом разделе подробно описан полный цикл авторизации в web-API ПОЛИНОМ:MDM средствами клиентской библиотеки Ascon.Polynom.Web.Api.Client: как устроена авторизация и как ею пользоваться. Пример, демонстрирующий все шаги, — Samples.WebApi.Authorization.

Авторизация реализуется классом AuthorizationService, а HTTP-вызовы сервисов API автоматически дополняются заголовком Authorization: Bearer делегирующим обработчиком AuthorizationMessageHandler. Писать ручные запросы и подставлять токен вручную не требуется.

Как устроена авторизация

  1. Клиентское приложение указывает адрес web-сервера и параметры в PolynomApiClientOptions.
  2. Приложение получает список хранилищ и выполняет вход с логином, паролем и идентификатором хранилища.
  3. Web-сервер возвращает пару токенов: токен доступа (access_token) и токен обновления (refresh_token).
  4. Токен доступа действует 10 минут. Библиотека хранит его, подставляет в запросы и умеет обновлять:
    • по таймеру — за RefreshTokenBeforeExpiry до истечения;
    • по ответу 401 Unauthorized — однократное обновление и повтор запроса.
  5. При выходе токены удаляются, а сессия закрывается на web-сервере (при настройке — и в общем сервисе авторизации, ОСА).

Важно. Срок действия токена доступа ограничен. Полагайтесь на автоматическое продление библиотеки, а не на ручное управление токеном.

Компоненты библиотеки

Компонент Назначение
PolynomApiClientFactory Фабрика клиентов для работы без DI; владеет сервисом авторизации и HTTP-обработчиком
PolynomApiClientOptions Параметры клиента: адрес, режим продления, хранение токенов
AuthorizationService Вход, выход, обновление и автопродление токена
AuthorizationMessageHandler Подставляет заголовок Authorization и повторяет запрос при 401
ITokenStorage, AuthTokens Хранение снимка токенов
ITokenProtector, TokenProtectorFactory Шифрование токенов (в памяти или на диске)
ILoginApiClientFactory Предоставляет LoginApiClient на время операции

Регистрация клиента

Режим без DI (фабрика)

Для консольных приложений используется PolynomApiClientFactory:

using Ascon.Polynom.Web.Api.Client;
using Ascon.Polynom.Web.Api.Client.Auth;

// Протектор токенов на базе Data Protection; ключи размещаются в каталоге.
var protector = TokenProtectorFactory.CreateDataProtection(
    Path.Combine(Path.GetTempPath(), "PolynomApiClientKeys"));

using var factory = new PolynomApiClientFactory(
    new PolynomApiClientOptions
    {
        BaseAddress = new Uri("http://localhost:5100"),
        PersistTokens = false,
        ProtectTokensInMemory = true,
        RefreshOnUnauthorized = true
    },
    protector);

Режим с DI

В приложениях ASP.NET Core клиенты регистрируются расширением AddPolynomApiClient:

builder.Services.AddPolynomApiClient(options =>
{
    options.BaseAddress = new Uri("https://polynom-mdm.local");
    options.SsoUrl = "https://sso.local";
    options.PersistTokens = true;
    options.TokenStoragePath = Path.Combine(AppContext.BaseDirectory, "tokens.protected");
});

Расширение регистрирует AuthorizationService, хранилище и протектор токенов, именованный HTTP-клиент с AuthorizationMessageHandler и все сервисы *ApiClient.

Параметры клиента

Свойство Тип По умолчанию Описание
BaseAddress Uri? — Базовый адрес web-API. Обязателен
SsoUrl string? — Адрес сервера ОСА для выхода из единой сессии
RefreshTokenBeforeExpiry TimeSpan 2 минуты За сколько до истечения продлевать токен
AutoRefreshToken bool true Автопродление токена по таймеру
RefreshOnUnauthorized bool true Обновлять токен и повторять запрос при 401
ProtectTokensInMemory bool true Шифрование токенов в памяти
PersistTokens bool false Сохранять токены между запусками
TokenStoragePath string? — Путь к файлу токенов (обязателен при PersistTokens)

Полный цикл авторизации

Ниже — сценарий, повторяющий пример Samples.WebApi.Authorization: список хранилищ → вход → обновление токена → выход. Метод возвращает признак успешной авторизации, а не печатает состояние.

using Ascon.Polynom.Web.Api.Client.Auth;
using Ascon.Polynom.Web.Api.Client.Enums;
using Ascon.Polynom.Web.Api.Client.Interfaces;
using Ascon.Polynom.Web.Api.Client.Models;

// Шаг 1. Список хранилищ (без авторизации) и выбор нужного.
var storages = await factory.CreateLoginApiClient().StorageDefinitions()
               ?? new List<IStorageDefinition>();

var storage = storages.FirstOrDefault(s => s.DisplayName == "Test")
    ?? throw new InvalidOperationException("Хранилище «Test» не найдено.");

// Шаг 2. Вход по логину и паролю.
var authResponse = await factory.Authorization.SignInAsync(new SignInRequest
{
    Login = "admin",
    Password = password,
    StorageId = storage.StorageId.ToString(),
    ClientType = ClientTypes.Client
});

if (string.IsNullOrEmpty(authResponse?.Access_token))
    throw new InvalidOperationException("Web-сервер не вернул токен доступа.");

// Шаг 3. Проверка состояния авторизации.
if (!factory.Authorization.IsAuthenticated)
    throw new InvalidOperationException("Авторизация не выполнена.");

// Шаг 4. Продление токена по токену обновления.
var refreshed = await factory.Authorization.UpdateTokenAsync();
if (!refreshed)
    throw new InvalidOperationException("Не удалось продлить токен доступа.");

// Шаг 5. Выход из сессии.
await factory.Authorization.SignOutAsync();

SignInAsync принимает SignInRequest и возвращает IAuthResponse с полями Access_token, Refresh_token и Expires_in. Перечисление ClientTypes задаёт тип клиента (Client, Importer, Editor, Administrator).

Вход по готовому токену доступа (в том числе ОСА)

Если токен доступа уже получен (например, из общего сервиса авторизации), вход выполняется без пароля:

var authResponse = await factory.Authorization.SignInByAccessTokenAsync(
    accessToken,
    new SignInRequest
    {
        Login = "user",
        StorageId = storage.StorageId.ToString(),
        ClientType = ClientTypes.Client
    },
    tokenFromSso: true);

Признак tokenFromSso: true запоминает токен ОСА, чтобы при выходе закрыть и сессию единой авторизации. Адрес ОСА задаётся параметром SsoUrl или методом SetSsoUrl. Настройка ОСА на стороне web-сервера описана в разделе Авторизация с помощью общего сервиса авторизации.

Обновление и автопродление токена

  • AutoRefreshToken = true запускает фоновый таймер: библиотека сама обновляет токен за RefreshTokenBeforeExpiry до истечения.
  • RefreshOnUnauthorized = true включает реакцию на ответ 401: однократное обновление токена и повтор исходного запроса. Повтор не выполняется для эндпоинтов авторизации.
  • Ручное обновление доступно методом UpdateTokenAsync:
var updated = await factory.Authorization.UpdateTokenAsync();
if (!updated)
    throw new InvalidOperationException("Токен обновления отсутствует или отклонён сервером.");

Выход из сессии

await factory.Authorization.SignOutAsync();

Метод закрывает сессию web-API и, если настроен ОСА и вход был по токену ОСА, — сессию единой авторизации. Только выход из ОСА выполняет SignOutSsoAsync.

Хранение токенов

Режим Реализация Когда использовать
В памяти InMemoryTokenStorage Кратковременные сценарии; токены не сохраняются на диск
На диске FileTokenStorage Повторный запуск без повторного ввода пароля (PersistTokens = true)

Токены шифруются протектором: ProtectTokensInMemory = true шифрует их в памяти, а PersistTokens = true вместе с TokenStoragePath сохраняет зашифрованный файл. Протектор создаётся фабрикой TokenProtectorFactory на базе Data Protection.

События состояния авторизации

Подписка на события позволяет приложению реагировать на вход и выход без опроса состояния.

var isAuthenticated = false;
var signedOut = false;

factory.Authorization.AuthenticationStateChanged += (_, value) => isAuthenticated = value;
factory.Authorization.SignedOut += (_, _) => signedOut = true;

await factory.Authorization.SignInAsync(signInRequest);

if (!isAuthenticated)
    throw new InvalidOperationException("Не удалось авторизоваться.");
Событие Тип Момент
AuthenticationStateChanged EventHandler<bool> Изменилось состояние авторизации
SignedOut EventHandler Выход выполнен

Обработка ошибок

Методы API вызывают EnsureSuccessStatusCode(), поэтому при неуспешном ответе выбрасывается HttpRequestException со статус-кодом в StatusCode. При ошибке входа AuthorizationService очищает сохранённые токены.

using System.Net;
using System.Net.Http;

try
{
    await factory.Authorization.SignInAsync(signInRequest);
}
catch (HttpRequestException ex) when (ex.StatusCode == HttpStatusCode.Unauthorized)
{
    // Неверные учётные данные — вызывающий код решает, повторить вход или отказать.
    throw new InvalidOperationException("Авторизация отклонена: проверьте логин и пароль.", ex);
}
catch (HttpRequestException ex)
{
    // Прочие ошибки транспорта пробрасываются с сохранением статус-кода и текста.
    throw new InvalidOperationException($"Ошибка обращения к web-API: {ex.StatusCode}.", ex);
}

Ссылки