Эффективное управление авторизацией OAuth в Azure API Management

При разработке приложения вы в большинстве случаев используете вызовы API для отправки и получения сообщений. Поэтому для использования API необходимо пройти процесс аутентификации и авторизации, если только это не публичный API. В процессе аутентификации/авторизации используется либо 1) ключ аутентификации, либо 2) маркер доступа через процесс OAuth.

Используя подход с ключом аутентификации, вы можете хранить его в безопасном месте, например, в службе Azure Key Vault, и получить ключ. Это относительно простой процесс. Однако если вам нужно использовать процесс OAuth, то все усложняется следующим процессом авторизации. Вот концептуальный процесс OAuth.

  1. Запрос кода авторизации
  2. Получение кода авторизации
  3. Запросите маркер доступа, предоставив код авторизации.
  4. Получение маркера доступа и маркера обновления
  5. Вызовите запрос API, предоставив маркер доступа
  6. Получите ответ API
  7. Запросить новый маркер доступа, предоставив маркер обновления, после истечения срока действия существующего маркера доступа
  8. Получите новый маркер доступа и новый маркер обновления
  9. Вызовите запрос API, предоставив новый маркер доступа
  10. Получите ответ API

Если вы хотите узнать больше о процессе аутентификации OAuth, пожалуйста, обратитесь к этому документу.

Поэтому разработчики должны реализовать эти процессы как часть разработки приложения. Возможно, вам повезет, если сервис, который вы хотите использовать, предлагает SDK. Если нет, то вам придется делать все это самостоятельно, что очень обременительно. А что если кто-то сделает все эти утомительные шаги за вас? Допустим, в безопасном месте кто-то выполняет весь процесс авторизации от вашего имени и просто возвращает маркер доступа. Если это произойдет, скорость разработки вашего приложения значительно возрастет. Azure API Management (APIM) недавно выпустила функцию предварительного просмотра под названием “Authorisations”, которая выполняет процесс OAuth от вашего имени. В этом посте я собираюсь обсудить эту функцию на примере приложения Blazor Web Assembly (WASM), размещенного на Azure Static Web Apps (SWA).

Вы можете загрузить пример кода приложения из этого репозитория GitHub:

Token Store: Azure API Management Authorizations

Предварительные условия

  • Для приложения Blazor: .NET SDK 6.0.300 или более поздняя версия.
  • Для приложения React: node.js v14 или более поздняя версия.

Начало работы

Секреты GitHub

Добавьте следующие GitHub Secrets в ваш репозиторий:

Dropbox App

В этом примере используется токен доступа, выданный Dropbox. Вам необходимо создать приложение для этого примера.

Автопилот

Это будет работать только в том случае, если это репозиторий станет публичным.

Нажмите кнопку ниже, чтобы создать и развернуть приложение Blazor WASM и приложение React одним движением. Запишите имя ресурса для дальнейшего использования.

После завершения вышеуказанной процедуры предоставления ресурсов запустите рабочий процесс GitHub Action. Убедитесь, что используете то же имя ресурса, что и…

Просмотр на GitHub

В репозитории есть два приложения – одно основано на Blazor WASM, а другое – на React. Я собираюсь использовать здесь образец Blazor WASM. Если вас заинтересовал образец приложения React, посетите статью в блоге моего коллеги Аарона Пауэлла.

Приложение веб-ассамблеи Blazor

Пожалуйста, обратитесь к документу Blazor Tutorial для ознакомления с Blazor WASM в целом. Вместо этого я рассмотрю компонент, заботящийся об авторизации OAuth и маркере доступа. Этот компонент принимает пользовательские данные, сохраняет их в виде файла формата .csv и загружает в DropBox. Приведенный ниже код Razor не представляет собой ничего особенного, но содержит форму для ввода данных пользователем. Когда пользователь заполняет форму и нажимает кнопку “Submit”, срабатывает событие OnFormSubmittedAsync (строка #3).

<div class="container-sm" style="max-width: 540px;">
  <h1>Blazor Lead Capture</h1>
  <form class="clearfix" @onsubmit="OnFormSubmittedAsync">
    <fieldset>
      <div>
        <label for="firstName" class="form-label">First name</label>
        <input type="text" class="form-control" id="firstName" name="firstName" placeholder="Justin" value="@userInfo.FirstName" @onchange="@(e => OnFieldChanged(e, "firstName"))" />
      </div>
      <div>
        <label for="lastName" class="form-label">Last name</label>
        <input type="text" class="form-control" id="lastName" name="lastName" placeholder="Yoo" value="@userInfo.LastName" @onchange="@(e => OnFieldChanged(e, "lastName"))" />
      </div>
    </fieldset>

    <fieldset>
      <div>
        <label htmlFor="email" class="form-label">Email</label>
        <input type="email" class="form-control" id="email" name="email" placeholder="bar@email.com" value="@userInfo.Email" @onchange="@(e => OnFieldChanged(e, "email"))" />
      </div>
      <div>
        <label htmlFor="phone" class="form-label">Phone</label>
        <input type="phone" class="form-control" id="phone" name="phone" placeholder="555-555-555" value="@userInfo.Phone" @onchange="@(e => OnFieldChanged(e, "phone"))" />
      </div>
    </fieldset>

    <fieldset>
      <button type="submit" class="btn btn-@componentUIInfo.ButtonColour" disabled="@(componentUIInfo.Submitting || string.IsNullOrWhiteSpace(userInfo.FirstName) || string.IsNullOrWhiteSpace(userInfo.LastName) || string.IsNullOrWhiteSpace(userInfo.Email) || string.IsNullOrWhiteSpace(userInfo.Phone))">
        <span>Submit</span>
        <span class="spinner-border spinner-border-sm" style="display:@componentUIInfo.DisplaySpinner;" role="status" aria-hidden="true"></span>
      </button>
    </fieldset>
  </form>

  <div class="alert alert-@componentUIInfo.AlertResult" style="display:@componentUIInfo.DisplayResult;">
    <h2>@componentUIInfo.MessageResult</h2>
    <button type="reset" class="btn btn-dark" @onclick="ResetFields">
      <span>Start Over?</span>
    </button>
  </div>
</div>
Вход в полноэкранный режим Выход из полноэкранного режима

Следующий блок @code { ... } блок – это код C#, взаимодействующий с компонентом Razor. Для краткости я оставил два метода. Первый метод – это обработчик события OnFormSubmittedAsync, вызываемый кнопкой “Submit”. Затем, внутри обработчика события, вызывается метод SaveToDropboxAsync, который берет на себя все заботы, включая выборку маркера доступа и сохранение в DropBox.

@code {
    ...
    protected async Task OnFormSubmittedAsync(EventArgs e)
    {
        ...
        await SaveToDropboxAsync().ConfigureAwait(false);
    }
Вход в полноэкранный режим Выход из полноэкранного режима

В методе SaveToDropboxAsync он сначала получает переменную окружения APIM_Endpoint (строка #4). Blazor WASM хранит все переменные окружения в файле appsettings.json, который я рассмотрю позже. Вызвав конечную точку APIM из appsettings.json, приложение получает маркер доступа к DropBox (строка #7), а экземпляр клиента DropBox загружает данные.

    private async Task SaveToDropboxAsync()
    {
        // Gets the APIM endpoint from appsettings.json
        var requestUrl = Configuration.GetValue<string>("APIM_Endpoint");

        // Gets the auth token from APIM
        var token = await Http.GetStringAsync(requestUrl).ConfigureAwait(false);

        // Builds contents.
        var path = $"/submissions/{DateTimeOffset.UtcNow.ToString("yyyyMMddHHmmss")}.csv";
        var contents = $"{userInfo.FirstName},{userInfo.LastName},{userInfo.Email},{userInfo.Phone}";
        var bytes = UTF8Encoding.UTF8.GetBytes(contents);

        // Uploads the contents.
        var result = default(FileMetadata);
        using(var dropbox = new DropboxClient(token))
        using(var stream = new MemoryStream(bytes))
        {
            result = await dropbox.Files.UploadAsync(path, WriteMode.Overwrite.Instance, body: stream).ConfigureAwait(false);
        }

        ...
    }
}
Вход в полноэкранный режим Выход из полноэкранного режима

Давайте посмотрим на файл appsettings.json, который содержит переменную окружения APIM_Endpoint. Это значение – конечная точка APIM для получения маркера доступа DropBox.

{
  "APIM_Endpoint": "https://<APIM_NAME>.azure-api.net/dropbox-demo/token?subscription-key=<APIM_SUBSCRIPTION_KEY>"
}
Вход в полноэкранный режим Выход из полноэкранного режима

Запустите приложение Blazor WASM на локальной машине.

dotnet watch run
Войдите в полноэкранный режим Выйдите из полноэкранного режима

Затем откройте веб-браузер и введите URL-адрес https://localhost:5001, после чего вы увидите страницу, как показано на рисунке:

Заполните форму и нажмите кнопку “Отправить”, после чего приложение сохранит данные формы в DropBox. Затем, если вы откроете Developer Tools вашего веб-браузера, вы сможете увидеть, как приложение Blazor WASM вызывает конечную точку APIM.

И вызов API возвращает маркер доступа к DropBox.

С помощью этого маркера доступа вы можете создать и сохранить файл. Вот результат:

Здесь, как вы помните, нет кодов для OAuth авторизации в DropBox. Вместо этого все начинается с вызова API, который напрямую получает маркер доступа. Так как же код может удалить все предварительные коды перед получением маркера доступа? Это и есть новая функция авторизации APIM, о которой идет речь в этом посте. Короче говоря, экземпляр APIM внутри выполняет все процессы, связанные с OAuth, от имени приложения Blazor WASM и просто возвращает маркер доступа.

Экземпляр Azure API Management

Давайте разберемся в новой функции APIM. Вы можете нажать кнопку ниже, чтобы предоставить все ресурсы в Azure сразу.

В качестве альтернативы вы можете запустить бицепсы, объявленные ниже. Давайте подробнее рассмотрим бицепс-файлы. Во-первых, объявите экземпляр APIM. Вы можете заметить, что он включает функцию Managed Identity (строки #13-15), о которой я расскажу позже в этой статье. Для удобства назовите экземпляр APIM token-store-demo-apim и задайте местоположение West Central US. Группа ресурсов для этого резерва установлена на rg-token-store-demo.

// APIM instance
resource apim 'Microsoft.ApiManagement/service@2021-08-01' = {
  name: 'token-store-demo-apim'
  location: 'westcentralus'
  sku: {
    name: 'Developer'
    capacity: 1
  }
  properties: {
    publisherName: 'John Doe'
    publisherEmail: 'john.doe@nomail.com'
  }
  identity: {
    type: 'SystemAssigned'
  }
}
Вход в полноэкранный режим Выход из полноэкранного режима

Следующим шагом будет политика CORS, поскольку Azure SWA напрямую обращается к конечной точке APIM. В узле inbound добавьте узел cors, под ним добавьте узел allowed-origins и узел origin со значением *. Убедитесь, что это только для демонстрации. Для большей безопасности при запуске в эксплуатацию следует добавить конкретный URL.

// Service Policy
resource apim_policy 'Microsoft.ApiManagement/service/policies@2021-08-01' = {
  parent: apim
  name: 'policy'
  properties: {
    value: service_policy
    format: 'xml'
  }
}

// Service Policy Definition
var service_policy = '''
<policies>
    <inbound>
        <cors allow-credentials="false">
            <allowed-origins>
                <origin>*</origin>
            </allowed-origins>
            <allowed-methods>
                <method>GET</method>
                <method>POST</method>
            </allowed-methods>
        </cors>
    </inbound>
    <backend>
        <forward-request />
    </backend>
    <outbound />
    <on-error />
</policies>'''
Вход в полноэкранный режим Выход из полноэкранного режима

После объявления экземпляра APIM определите API и его работу. Для API serviceUrl устанавливается базовый URL DropBox API, а конечная точка операции устанавливается на /token, которая возвращает токен доступа. Таким образом, вся конечная точка APIM может выглядеть как https://token-store-demo-apim.azure-api.net/dropbox-demo/token.

// API
resource api 'Microsoft.ApiManagement/service/apis@2021-08-01' = {
  name: 'dropbox-demo'
  parent: apim
  properties: {
    serviceUrl:'https://api.dropboxapi.com'
    path: 'dropbox-demo'
    displayName:'dropbox-demo'
    protocols:[
      'https'
    ]
  }
}

// Operation
resource api_gettoken 'Microsoft.ApiManagement/service/apis/operations@2021-08-01' = {
  name: 'gettoken'
  parent: api
  properties: {
    method: 'GET'
    urlTemplate: '/token'
    displayName: 'gettoken'
  }
}
Вход в полноэкранный режим Выйти из полноэкранного режима

Однако такой конечной точки не существует в DropBox API. Поэтому добавьте политику операции, чтобы эта операция работала.

// Operation Policy
resource api_gettoken_policy 'Microsoft.ApiManagement/service/apis/operations/policies@2021-08-01' = {
  parent: api_gettoken
  name: 'policy'
  properties: {
    value: operation_token_policy
    format: 'xml'
  }
}
Войти в полноэкранный режим Выйти из полноэкранного режима

Следующий XML-документ политики операций объясняет основную идею этой новой функции APIM. Добавьте узел get-authorization-context в inbound. Он имеет следующие атрибуты.

Как вы можете видеть, экземпляр APIM включил функцию Managed Identity, соответствующую identity-type. И provider-id, и authorisation-id будут использоваться позже. Атрибут context-variable-name имеет значение auth-context. Он используется в узле return-response, который хранит значение маркера доступа. В целом, эта политика операций заботится о получении маркера доступа от имени приложения SWA.

// Operation Token Policy Definition
var operation_token_policy = '''
<policies>
    <inbound>
        <base />
        <get-authorization-context provider-id="dropbox-demo" authorization-id="auth" context-variable-name="auth-context" ignore-error="false" identity-type="managed" />
        <return-response>
            <set-body>@(((Authorization)context.Variables.GetValueOrDefault(&quot;auth-context&quot;))?.AccessToken)</set-body>
        </return-response>
    </inbound>
    <backend>
        <base />
    </backend>
    <outbound>
        <base />
    </outbound>
    <on-error>
        <base />
    </on-error>
</policies>'''
Вход в полноэкранный режим Выход из полноэкранного режима

Вы определили экземпляр APIM. Теперь вам нужен экземпляр приложения SWA для приложения Blazor WASM.

Экземпляр Azure Static Web Apps

Для размещения приложения Blazor WASM необходимо создать экземпляр Azure SWA. Вот код бицепса для этого. Сначала дайте приложению имя token-store-demo-blazor-swa и местоположение Central US под группой ресурсов rg-token-store-demo.

// SWA instance
resource sttapp 'Microsoft.Web/staticSites@2021-02-01' = {
  name: 'token-store-demo-blazor-swa'
  location: 'centralus'
  sku: {
    name: 'Free'
  }
  properties: {
    allowConfigFileUpdates: true
    stagingEnvironmentPolicy: 'Enabled'
  }
}
Вход в полноэкранный режим Выход из полноэкранного режима

Развертывание приложений Blazor WASM в Azure Static Web Apps Instance

У вас есть новый экземпляр ASWA. Пришло время развернуть приложение Blazor WASM, которое находится в каталоге src/frontend/blazor. Первым шагом должно стать добавление файла appsettings.json, который содержит конечную точку APIM для маркера доступа. Следующие команды помогут получить URL конечной точки APIM.

# Get APIM gateway URL
rg_name=rg-token-store-demo
apim_name=token-store-demo-apim

gateway_url=$(az apim show -g $rg_name -n $apim_name --query "gatewayUrl" -o tsv)

# Get APIM subscription key
subscription_id=$(az account show --query "id" -o tsv)
apim_secret_uri=/subscriptions/$subscription_id/resourceGroups/$rg_name/providers/Microsoft.ApiManagement/service/$apim_name/subscriptions/master/listSecrets
api_version=2021-08-01

subscription_key=$(az rest --method post --uri $apim_secret_uri?api-version=$api_version | jq '.primaryKey' -r)

# Build APIM endpoint
apim_endpoint=$gateway_url/dropbox-demo/token?subscription-key=$subscription_key
Войти в полноэкранный режим Выйти из полноэкранного режима

URL конечной точки APIM находится в переменной apim_endpoint. Поэтому переименуйте файл appsettings.sample.json в каталоге src/frontend/blazor/wwwroot в appsettings.json и обновите конечную точку.

{
  "APIM_Endpoint": "<apim_endpoint>"
}
Вход в полноэкранный режим Выход из полноэкранного режима

Соберите и создайте артефакт приложения Blazor WASM.

dotnet restore ./src/frontend/blazor
dotnet build ./src/frontend/blazor
dotnet publish ./src/frontend/blazor -c Release -o ./src/frontend/blazor/bin
Вход в полноэкранный режим Выход из полноэкранного режима

Остался еще один шаг для развертывания. Получите ключ развертывания, выполнив приведенную ниже команду:

swa_key=$(az staticwebapp secrets list 
    -g rg-token-store-demo 
    -n token-store-demo-blazor-swa 
    --query "properties.apiKey" -o tsv)
Войти в полноэкранный режим Выйти из полноэкранного режима

Наконец, разверните приложение Blazor WASM, выполнив команду.

swa deploy -a ./src/frontend/blazor/bin/wwwroot -d $swa_key --env default
Войти в полноэкранный режим Выйти из полноэкранного режима

Хотя до сих пор все шло отлично, после отправки формы вы можете увидеть ошибку ниже:

Это потому, что вы еще не дали согласие на использование приложения DropBox. Последним шагом будет согласие.

Согласие на приложение DropBox

Создайте приложение DropBox, следуя этому документу, и вы получите App key и App secret.

Вам понадобятся оба значения для согласия в экземпляре APIM. Нажмите “Authorisations (preview)” на лезвии.

Пока еще нет приложений для авторизации. Поэтому нажмите кнопку “Создать”.

Вы можете вспомнить, как вы настраивали узел get-authorisation-context для политики операций. Пришло время использовать их.

  • Введите dropbox-demo в поле “Имя провайдера”.
  • Выберите DropBox в поле “Identity provider”.
  • Введите значение ключа приложения DropBox в поле “Идентификатор клиента”.
  • Введите значение секрета приложения DropBox в поле “Секрет клиента”.
  • Введите files.metadata.write files.content.write files.content.read в поле “Scopes”.
  • Введите auth в поле “Имя авторизации”.

Нажмите кнопку “Создать” и получите URL перенаправления.

Добавьте URL перенаправления в приложение DropBox.

Вернитесь к экземпляру APIM и войдите в приложение DropBox. Затем продолжите работу со всплывающим окном, последовательно нажав на кнопки “Продолжить” и “Разрешить” и “Разрешить доступ”.

После этого вы увидите сообщение об успехе.

Приложение DropBox теперь авторизовано. Но экземпляр APIM еще не авторизован для доступа к приложению DropBox. Поскольку на экземпляре APIM включена функция Managed Identity, давайте воспользуемся ею. Выберите “Управляемая идентификация” и нажмите кнопку “Добавить участников”.

Найдите экземпляр APIM и добавьте его.

Теперь и APIM, и DropBox могут взаимодействовать друг с другом.

Вы видите, что приложение DropBox авторизовано?

Давайте проверим конечную точку, работает она или нет. Перейдите в меню “APIs” ➡️ “dropbox-demo” ➡️ “gettoken” и нажмите кнопку “Send”.

После этого вы увидите, что маркер доступа успешно выдан.

Давайте вернемся в приложение ASWA и заполним форму. На этот раз ошибки нет.

И загруженный файл появляется на DropBox!


До сих пор мы обсуждали новую функцию авторизации OAuth в Azure API Management. В конечном итоге нам нужен токен доступа через процесс OAuth. APIM выполняет этот процесс. Поэтому мы можем сэкономить огромное количество времени, не внедряя эту функцию в наше приложение. Поскольку в настоящее время это предварительный просмотр, некоторые функции могут быть готовы, но они будут постоянно улучшаться.

Если вы хотите узнать больше об этой функции управления авторизацией APIM OAuth, посетите документ ниже:

  • Управление авторизациями Azure API Management

Оцените статью
Procodings.ru
Добавить комментарий